JDBC概述
JDBC的全称为:Java DataBase Connectivity (Java数据库连接),是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,你可以认为这是一套Java连接数据库的规范。在以前没有JDBC的时候,Java程序员想要连接数据库就必须要了解该数据库的具体的驱动程序,如图示例:
后来,Sun 公司提供了JDBC的接口规范——JDBC API 后,数据库厂商就需要根据该接口规范来实现自己的驱动程序,我们在进行数据库开发的时候,只需要使用JDBC接口提供的方法即可:
JDBC的API
JDBC入门程序
首先创建数据库,数据表以及插入记录,sql语句如下:
CREATE DATABASE IF NOT EXISTS jdbctest DEFAULT CHARACTER SET UTF8;
USE jdbctest;
CREATE TABLE goods(
id INT NOT NULL AUTO_INCREMENT KEY,
name VARCHAR(20) NOT NULL,
price FLOAT(6,1) NOT NULL,
desp VARCHAR(30) NOT NULL
);
INSERT goods(name,price,desp) VALUES('手机',2000.0,'黑色,存储容量32G'),
('冰箱',1500.0,'银色,对开门'),
('洗衣机',3000.0,'滚筒'),
('空调',4000,'变频空调');
现在我们的需求是在我们的程序中查询并打印出价格在3500以下的商品信息。
首先我们需要先下载mysql connector 的 jar包。在IDEA上面导入jar包,具体步骤为:
ctrl + alt + shift + s
打开Project Structure,接下来按照图片的顺序进行配置即可 该图片截取自CSDN博客上面末尾带空格的bearBaby的文章IDEA使用JDBC连接MySQL数据库详细教程
示例程序如下:
import org.junit.Test;
import java.sql.*;
public class JDBCDemo {
@Test
public void test() {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "root");
statement = connection.createStatement();
String sql = "SELECT * FROM goods\n" +
"WHERE price <= 3500;";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
float price = resultSet.getFloat("price");
String desp = resultSet.getString("desp");
System.out.println(id + " " + name + " " + price + " " + desp);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// JDBC 资源的释放
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
}
DriverManager
DriverManager是驱动管理类,它的主要作用有两个:
- 注册驱动
- 获得连接
注册驱动的两种方式:
第一种:
DriverManager.registerDriver(new Driver());
第二种:
Class.forName("com.mysql.jdbc.Driver");
在实际开发中,应该使用反射的写法,因为第一种方式会导致DriverManager类注册驱动两次,下面的程序是DriverManager类的源代码中的一部分,可以看到在静态代码块中,已经实现了注册驱动,我们只要使用反射加载DriverManager类即可达到注册驱动的目的。
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
DriverManager类的第二个主要的作用就是获得连接,使用如下程序:
DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest","root","root");
该程序会返回一个Connection对象。getConnection方法里要传入三个参数,第一个参数是url,第二个参数是连接数据库的username,第三个参数是password。对于第一个参数url的写法如下:
协议:子协议://主机名:端口号/数据库名称
url 的写法应该牢记!
Connection
调用DriverManager的getConnection方法就可以获得Connection对象。Connection对象的作用又是什么呢?
Connection对象的主要作用有如下几点:
- 创建用来执行SQL语句的对象
1. Statement createStatement()
2. PreparedStatement prepareStatement(String sql)
3. CallableStatement prepareCall(String sql)
createStatement方法可以执行SQL语句,prepareStatement可以进行预编译并执行。相比prepareStatement方法,createStatement方法无法防止SQL注入,prepareStatement方法解决了SQL注入的漏洞。prepareCall方法是用来执行SQL中的存储过程。
- 进行事物的管理
Connection对象除了可以创建用来执行SQL语句的对象,还可以进行事务的管理。
setAutoCommit(boolean autoCommit)
commit()
rollback()
setAutoCommit方法中可以传入一个布尔值,该方法用来设置事务是否自动提交;commit方法是事务提交的方法;rollback()是进行事务的回滚。
Statement
Statement对象的主要作用有两个:
- 执行SQL语句
- 执行批处理操作
Statement对象主要的执行SQL语句的方法有:
boolean execute(String sql)
ResultSet executeQuery(String sql)
int executeUpdate(String sql)
execute方法是用来执行SQL语句的;executeQuery则是用来执行SQL中的SELECT语句的,也就是执行SQL的查询操作,返回的ResultSet是查询的结果集;executeUpdate方法用来执行SQL中的insert/update/delete语句,也就是说executeUpdate方法主要执行SQL的增删改操作,其返回的结果是int类型,代表增删改操作影响的具体的行数。
除了执行SQL语句,Statement对象还可以执行批处理操作
主要方法如下:
addBatch(String sql)
executeBatch()
clearBatch()
addBatch方法可以将sql语句添加到批处理中;executeBatch方法可以将一群添加到批处理的sql语句执行;clearBatch方法则是将添加到批处理的sql语句清除。
ResultSet
ResultSet对象是结果集,它的作用很简单就是获取查询到的结果。什么是结果集?结果集其实就是查询语句(SELECT)查询出的结果的封装。ResultSet中的主要方法如下:
next()
getXXX()
getObject()
next方法用来判断是否有下一行记录,它的作用类似于迭代器,可以用来进行结果集的遍历;getXXX表示针对不同类型的数据可以使用可以使用getXXX来获取数据,比如你要获取String类型的数据则可以使用getString();你要获取int类型的数据则可以使用getInt()。而getObject()则是通用的方法,我们可以使用getObject()来获取任意类型的数据。
JDBC的资源的释放
JDBC程序运行完毕之后,切记要释放程序在运行过程中,创建的那些于数据库进行交互的对象,这些对象通常是ResultSet,Statement和Connection对象。
尤其是Connection对象,它是非常稀有的资源,用完之后必须要马上释放,如果Connection不能及时的正确关闭,极其容易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早释放。代码如下:
// JDBC 资源的释放
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
试想一下,为什么在close方法执行过后,还需要手动将connection置为null值呢?原因就在于,Connection对象是非常稀有的资源,用完需要马上释放。当Connection对象调用close方***释放此资源,但是还没有被JVM垃圾回收。如果在最后手动将Connection对象置为null,则会让垃圾回收机制更早地进行回收。
JDBC的CRUD操作
向数据库中插入记录
依然对数据表goods进行操作
现在向表中插入一条记录:
商品名称:耳机
商品价格:200.0
商民描述:蓝牙耳机
代码如下:
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo2 {
@Test
public void test(){
Connection connection = null;
Statement statement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8","root","root");
statement = connection.createStatement();
String sql = "INSERT goods(name,price,desp) VALUES('耳机',200.0,'蓝牙耳机');";
int result = statement.executeUpdate(sql);
if(result > 0){
System.out.println("insert succeed");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
}
这里注意:jdbc向mysql数据库中插入数据的时候,查询发现是出现乱码,只需要在getConnection的时候将url的后面加上?useUnicode=true&characterEncoding=utf8
即可。
插入数据成功~
修改数据库中的记录
数据表现有数据如下:
将id = 2的记录修改为:
name:电视
price:3000.0
desp:4k高清液晶电视
代码如下:
import org.junit.Test;
import java.sql.*;
public class JDBCDemo3 {
@Test
public void test() {
// 修改数据表的记录
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8", "root", "root");
statement = connection.createStatement();
String sql1 = "UPDATE goods SET name = '电视',price = 3000.0,desp = '4k高清液晶电视' WHERE id = 2";
String sql2 = "SELECT * FROM goods";
int result = statement.executeUpdate(sql1);
if (result > 0) {
System.out.println("update succeed");
resultSet = statement.executeQuery(sql2);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
float price = resultSet.getFloat("price");
String desp = resultSet.getString("desp");
System.out.println(id + " " + name + " " + price + " " + desp);
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
}
执行代码成功:
update succeed
1 手机 2000.0 黑色,存储容量32G
2 电视 3000.0 4k高清液晶电视
3 洗衣机 3000.0 滚筒
4 空调 4000.0 变频空调
6 耳机 200.0 蓝牙耳机
删除数据库中的记录
继续使用goods表
现在将id = 3的记录删除
代码如下:
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo4 {
@Test
public void test(){
// 删除数据表中的记录
Connection connection = null;
Statement statement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest","root","root");
statement = connection.createStatement();
String sql = "DELETE FROM goods where id = 3";
int result = statement.executeUpdate(sql);
if(result > 0){
System.out.println("delete succeed");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
}
执行代码成功,再次查询goods表显示如下:
查询数据库中的记录
还是对goods表进行操作,现在的需求是查询id = 4的这条记录。
代码如下:
import org.junit.Test;
import java.sql.*;
public class JDBCDemo5 {
@Test
public void test() {
// 查询数据表中的数据
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "root");
statement = connection.createStatement();
String sql = "SELECT * FROM goods WHERE id = 4;";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
float price = resultSet.getFloat("price");
String desp = resultSet.getString("desp");
System.out.println(id + " " + name + " " + price + " " + desp);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
}
查询成果,返回结果:
4 空调 4000.0 变频空调
封装成工具类
像上述示例中的几个程序,我们都需要去写复杂的诸如url,mysql的登录名与密码等信息,而且对于释放资源的程序都是一样的,如果每次都去写则十分麻烦,最好的方法就是将代码封装成工具类,这样就可以非常简便地使用工具类代替我们去做这些重复的工作。
封装好的工具类如下:
JDBCUtils 类
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
private static final String DRIVERCLASS;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static {
// 加载properties文件并解析
Properties properties = new Properties();
// 类加载器获取文件的输入流
try {
properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
} catch (IOException e) {
e.printStackTrace();
}
DRIVERCLASS = properties.getProperty("driverClass");
URL = properties.getProperty("url");
USERNAME = properties.getProperty("username");
PASSWORD = properties.getProperty("password");
}
/**
* 注册驱动
*/
public static void registDriver() throws ClassNotFoundException {
Class.forName(DRIVERCLASS);
}
/**
* 获取连接
*/
public static Connection getConnection() throws Exception {
registDriver();
return DriverManager.getConnection(URL, USERNAME, PASSWORD);
}
/**
* 释放资源
*/
public static void release(Statement statement,Connection connection){
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
// overload release
public static void release(ResultSet resultSet,Statement statement,Connection connection){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
jdbc.properties 文件配置
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8
username=root
password=root
TestJDBCUtils 类
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBCUtils {
// 测试工具类
@Test
public void test(){
Connection connection = null;
Statement statement = null;
try {
connection = JDBCUtils.getConnection();
statement = connection.createStatement();
String sql = "INSERT goods VALUES(NULL,'烤箱',1000.0,'专门烤鸡胸肉');";
int result = statement.executeUpdate(sql);
if(result > 0){
System.out.println("insert succeed");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(statement,connection);
}
}
}
SQL注入漏洞
什么是SQL注入漏洞
首先我们来看一段程序,该程序模拟的是用户登录情景,用到的数据表是user表:
import jdbcutils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class SQLInjectionVulnerabilityTest {
@Test
public void testSQLInjecton(){
if(SQLInjectionVulnerabilityTest.login("aaa","111")){
System.out.println("用户登录成功");
}else{
System.out.println("用户登录失败");
}
}
public static boolean login(String username, String password) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
boolean result = false;
try {
connection = JDBCUtils.getConnection();
statement = connection.createStatement();
String sql = "SELECT * FROM user where username = '" + username + "' AND password = '" + password + "';";
resultSet = statement.executeQuery(sql);
result = resultSet.next() ? true : false;
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(resultSet,statement,connection);
}
return result;
}
}
因为数据表中有username为张三的用户,而且张三的密码为111,所以返回的结果自然是用户登录成功
那么,什么是SQL注入呢?在我们知道username的情况下,假设我们不知道用户的密码:
import jdbcutils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class SQLInjectionVulnerabilityTest {
@Test
public void testSQLInjecton(){
if(SQLInjectionVulnerabilityTest.login("aaa ' or '1 = 1","xjbx")){
System.out.println("用户登录成功");
}else{
System.out.println("用户登录失败");
}
}
public static boolean login(String username, String password) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
boolean result = false;
try {
connection = JDBCUtils.getConnection();
statement = connection.createStatement();
String sql = "SELECT * FROM user where username = '" + username + "' AND password = '" + password + "';";
resultSet = statement.executeQuery(sql);
result = resultSet.next() ? true : false;
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(resultSet,statement,connection);
}
return result;
}
}
如果在登录用户名的输入框中,我们输入了aaa ' or '1 = 1
这样的语句,那么我们需要查询的sql语句就会为:
SELECT * FROM user where username = 'aaa' or '1 = 1' AND password = 'xjbx';
因为or运算的断路特性,这条语句必然会执行成功,所以即使密码输入错误,但是我们通过这种SQL注入的方式也可以返回登录成功
JDBC的SQL注入漏洞的解决
解决SQL注入的方法就是弃用Statement对象改用PreparedStatement。
PreparedStatement是Statement的子接口,它的实例对象可以通过调用Connection.preparedStatement(sql)方法来获得,相对于Statement对象而言:
- PreparedStatement可以避免SQL注入的问题
- Statement会使数据库频繁地编译SQL,可能造成数据库的缓冲区溢出。PreparedStatement则可以对SQL进行预编译,从而提高数据库的执行效率
- PreparedStatement对于SQL中的参数,允许使用占位符的形式进行替换,从而简化SQL语句的编写,并且能够有效避免SQL注入。
示例程序如下:
import jdbcutils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
public class SQLInjectionVulnerabilityTest {
@Test
public void testSQLInjecton() {
if (SQLInjectionVulnerabilityTest.loginProtectSQLInjection("aaa ' or '1 = 1", "xjbx")) {
System.out.println("用户登录成功");
} else {
System.out.println("用户登录失败");
}
}
public static boolean loginProtectSQLInjection(String username, String password) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
boolean result = false;
try {
connection = JDBCUtils.getConnection();
String sql = "SELECT * FROM user WHERE username = ? AND password = ?;";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
resultSet = preparedStatement.executeQuery();
result = resultSet.next() ? true : false;
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(resultSet,preparedStatement,connection);
}
return result;
}
}
带有SQL注入的语句测试结果如下:
将username变为aaa,password变为111,即正确的用户格式,测试情况如下:
PreparedStatement
插入记录
依旧对user表进行操作:
向user表中插入一条数据:
username : ddd
password:444
name:王五
代码如下:
import jdbcutils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class PreparedStatementTest1 {
@Test
public void test(){
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();
String sql = "INSERT INTO user VALUES(NULL,?,?,?);";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"ddd");
preparedStatement.setString(2,"444");
preparedStatement.setString(3,"王五");
int res = preparedStatement.executeUpdate();
if(res > 0){
System.out.println("INSERT SUCCEED");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(preparedStatement,connection);
}
}
}
插入记录的结果成功。
更新与删除记录
依旧对user表进行操作,现在的需求是:将id = 4的用户的name字段的值更新为赵六;代码如下:
import jdbcutils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class PreparedStatementTest1 {
@Test
public void testUpdate(){
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();
String sql = "UPDATE user SET name = ? WHERE uid = ?;";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"赵六");
preparedStatement.setInt(2,4);
int res = preparedStatement.executeUpdate();
if(res > 0){
System.out.println("UPDATE SUCCEED");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(preparedStatement,connection);
}
}
@Test
public void test(){
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();
String sql = "INSERT INTO user VALUES(NULL,?,?,?);";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"ddd");
preparedStatement.setString(2,"444");
preparedStatement.setString(3,"王五");
int res = preparedStatement.executeUpdate();
if(res > 0){
System.out.println("INSERT SUCCEED");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(preparedStatement,connection);
}
}
}
执行结果成功:
再来看删除记录的操作:
现在我们将uid = 4的记录删除:
代码如下:
@Test
public void testDelete(){
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();
String sql = "DELETE FROM user WHERE uid = ?;";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,4);
int res = preparedStatement.executeUpdate();
if(res > 0){
System.out.println("DELETE SUCCEED");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(preparedStatement,connection);
}
}
执行语句成功:
查询记录
还是以user表为例,查询uid = 3的记录
代码如下:
@Test
public void testQuery(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
String sql = "SELECT * FROM user WHERE uid = ?;";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,3);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int id = resultSet.getInt("uid");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String name = resultSet.getString("name");
System.out.println(id + " " + username + " " + password + " " + name);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(resultSet,preparedStatement,connection);
}
}
查询成功:
数据库连接池C3P0
连接池概述
连接池是创建和管理一个连接的缓冲池技术,这些连接准备好被任何需要他们的线程使用。那么为什么要使用连接池呢?
如果没有连接池,直接由应用程序获取连接的示例图是这样的:
这样做的缺点就是:用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,而且创建时间也比较长。如果网站一天有十万的访问量,数据库就需要创建十万次连接,极大地浪费了数据库的资源,并且很容易造成数据库服务器内存溢出。
而有了数据库连接池,则会变为这样:
数据库连接池是一块内存,这块内存中存者许多连接对象,请求不会直接面向数据库,而是会从连接池里面调用连接对象,使用之后再返还给连接池,这样我们就大大节省了数据库创建连接的消耗,达到了节省资源的作用。
C3P0 连接池的使用
手动创建连接池
我们可以直接手动创建连接池
@Test
public void demo1(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8");
dataSource.setUser("root");
dataSource.setPassword("root");
// 设置连接池最大的连接数量
dataSource.setMaxPoolSize(20);
// 设置连接池初始化的连接数量
dataSource.setInitialPoolSize(5);
connection = dataSource.getConnection();
String sql = "SELECT * FROM user;";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int id = resultSet.getInt("uid");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
System.out.println(id + " " + username + " " + password);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.release(resultSet,preparedStatement,connection);
}
}
对于上述程序的DriverClass或者是JdbcUrl,User,Password等参数,我们还可以进一步优化,将其写入到配置文件中。
使用配置文件的形式
首先我们需要下载C3P0的jar包并添加到Dependencies。然后配置好c3p0-config.xml文件,配置格式如下:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
</c3p0-config>
然后封装好我们的工具类:
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.*;
public class JDBCUtils2 {
private static final ComboPooledDataSource combo = new ComboPooledDataSource();
/**
* 获取连接
*/
public static Connection getConnection() throws Exception {
return combo.getConnection();
}
/**
* 释放资源
*/
public static void release(Statement statement,Connection connection){
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
// overload release
public static void release(ResultSet resultSet,Statement statement,Connection connection){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
我们的程序如下:
@Test
public void demo2(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils2.getConnection();
String sql = "SELECT * FROM user;";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int id = resultSet.getInt("uid");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
System.out.println(id + " " + username + " " + password);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils2.release(resultSet,preparedStatement,connection);
}
}
比起手动创建连接池,这样做的优点是,只有一个连接池对象,不需要每次都创建连接池。