数据库连接池
1. 数据库连接池是什么
线程池:其实就是一个容器(池子)放了很多线程。因为我们每一次在使用线程的时候,都需要自己去创建一个线程,然后使用完了以后就把这个线程销毁了,这种做法明显是比较浪费资源的,而且效率也不高,所以我们利用这种池化的思想,把线程维护在一个线程池中,当我们需要使用线程的时候我们从线程池里面获取一个线程,然后执行任务,这个线程使用完了之后不会去销毁,而是放到线程池中。
我们的数据库连接池也是一样的,在一个数据库连接池中,维护很多个数据库连接(具体来说就是Connection),当我们需要使用数据库连接的时候,我们从数据库连接池里面去获取一个数据库连接,然后用他处理SQL请求,当我们使用完了这个连接以后,再把这个连接对象返回数据库连接池。
2. 自己手动实现一个数据库连接池
package com.cskaoyan.connection;
import com.cskaoyan.utils.JDBCUtils;
import java.sql.Connection;
import java.util.LinkedList;
// 自己去写一个数据库连接池
public class MyConnectionPool {
// 一个装 connection对象的链表
// 从头部取,从尾部存
static LinkedList<Connection> connectionPools;
// 初始大小
final static int INIT_SIZE = 10;
// 临界大小
final static int MIN_SIZE = 5;
// 每次扩容的大小
final static int ADD_SIZE = 10;
static {
connectionPools = new LinkedList<>();
addCapcity(INIT_SIZE);
}
// 获取连接
public static Connection getConnection(){
// 自动扩容
if (connectionPools.size() < MIN_SIZE) {
addCapcity(ADD_SIZE);
}
Connection connection = connectionPools.removeFirst();
return connection;
}
// 扩容方法
private static void addCapcity(int size) {
if (size < 1) {
return;
}
for (int i = 0; i < size; i++) {
Connection connection = JDBCUtils.getConnection();
connectionPools.addLast(connection);
}
}
// 回收连接
public static void recyleConnection(Connection connection) {
connectionPools.addLast(connection);
}
}
还需要优化的地方:
-
可以把一些静态的成员变量写到配置文件中,通过配置文件来设置,会更加灵活
-
连接池里面的连接会不断增多,但是增多之后没有一个超时自动回收的机制
-
我们没有给连接池里面的连接数量定义上限
-
我们自己实现的数据库连接池需要实现
javax.sql.Datasource
这个接口这个接口主要的目的其实就是给我们定义好 获取连接的方法
那为什么Datasource这个接口没有定义回收连接的方法呢?
用户用完了连接池当中的连接,有可能会自己先关闭再放回连接池当中,返回给连接池的就只是一个被关闭的连接
3. 使用第三方开源的数据库连接池
3.1 DBCP
是Apache下面的一个开源项目,是一个比较老的开源的数据库连接池。目前在公司里面的新项目中,基本不会使用它
-
导包
-
配置
#连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/33th username=root password=123456 #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=20 #<!-- 最大空闲连接 --> maxIdle=20 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=utf8;useSSL=false;serverTimezone=Aisa/Shanghai #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=REPEATABLE_READ # set global/session transaction isolation level read uncommitted
-
使用
package com.cskaoyan.utils; import org.apache.commons.dbcp.BasicDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBCPUtils { // 声明一个数据库连接池(接口) static DataSource dataSource; static { try { Properties properties = new Properties(); // 读取配置文件 FileInputStream fileInputStream = new FileInputStream("dbcp.properties"); properties.load(fileInputStream); dataSource = BasicDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } // 获取连接 public static Connection getConnection(){ Connection connection = null; try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
3.2 C3P0
-
导包
-
配置
我们使用C3p0数据库连接池,需要在src目录下配置一个名字叫做 c3p0-config.xml 的文件,文件里位置不能改变,文件的名字也不能改变
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <!-- 在xml文件里面,有一些特殊字符,需要写他的转义字符 > > < < & & >= >= --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/33th?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">123456</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> </c3p0-config>
-
使用
package com.cskaoyan.utils; import com.mchange.v2.c3p0.ComboPooledDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class C3p0Utils { // 数据库连接池对象 private static DataSource dataSource; static { dataSource = new ComboPooledDataSource(); } public static Connection getConnection(){ // 注意:这里返回的对象不是一个JDBC4Connection对象 Connection connection = null; try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
3.3 Druid
这个是阿里巴巴开源的一个数据库连接池,以性能强大,稳定,还带有性能监控功能而风靡。目前是有apache来维护。
-
导包
-
配置
这个数据库连接池的配置和我们DBCP是有点类似的,都是通过properties配置文件来配置
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/33th?useSSL=false&characterEncoding=utf8 username=root password=123456
-
使用
package com.cskaoyan.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DruidUtils { private static DataSource dataSource; static { try { // 加载配置文件 Properties properties = new Properties(); FileInputStream fileInputStream = new FileInputStream("druid.properties"); properties.load(fileInputStream); // 通过配置文件里面的配置,来创建数据库连接池 dataSource = DruidDataSourceFactory.createDataSource(properties); }catch (Exception ex) { System.out.println("初始化数据库连接池失败..."); ex.printStackTrace(); } } // 获取连接的方法 public static Connection getConnection(){ Connection connection = null; try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
DBUtils
1. 介绍
DBUtils是一个对我们JDBC的公共操作做了一个简单封装的框架或者是叫做工具类库。
DBUtils 有三个核心的组件:
QueryRunner:该类提供了 DML 和 DQL 的 API。
ResultSetHandler:该接口定义如何封装结果集。
DbUtils:一个简单的工具类,简化了关闭资源和事务处理,可以简化JDBC操作的模板代码。
框架是什么呢?
框架就是一个半成品的软件。这个半成品的软件有一些基础的功能,但是不能作为一个软件独立运行。我们去使用框架其实是在框架的基础之上进行二次开发,开发出适合我们业务需求的产品(软件)。框架就好比毛坯房,不能住人,我们的产品就是精装修房,可以住人。
DBUtils的功能比较简单,里面提供了很多可以方便我们进行JDBC操作的一个API,这些API的出现,简化了我们使用JDBC编程的代码量。
2. 使用
// 获取连接
Connection connection = JDBCUtils.getConnection();
// 获取statement对象
// 这个statement对象是用来去封装sql语句,使sql语句变成一个网络请求,然后发送给MySQL服务器
Statement statement = connection.createStatement();
// 发送SQL请求
ResultSet resultSet = statement.executeQuery("select id,name,p_id from city");
// 获取结果集、解析结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int pId = resultSet.getInt("p_id");
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("pId:" + pId);
}
// 关闭资源
JDBCUtils.closeSources(connection,statement,resultSet);
DBUtils这个工具类库其实就是可以帮助我们更加简单的去执行SQL语句,更加方便的去解析结果集
-
导包
-
配置
DBUtils不需要额外的配置
-
使用
具体而言就是使用以下的三个类 来简单我们的JDBC的操作
2.1 DbUtils
我们发现,这个DBUtils这个类里面其实就是封装了一些简单的方法,帮助我们去关闭资源、提交事务、回滚事务等等。
大多数情况下,我们都不使用DBUtils这个类,这个类基本没什么用。
2.2 QueryRunner
这个类是帮助我们去执行SQL语句的。
// 构造方法
QueryRunner queryRunner = new QueryRunner();
QueryRunner queryRunner = new QueryRunner(Datasource datasource);
// 执行sql
queryRunner.update(String sql);
queryRunner.update(Connection conn, String sql);
queryRunner.update(String sql,Object param);
queryRunner.update(Connection conn, String sql,Object param);
queryRunner.update(String sql,Object... param);
queryRunner.update(Connection conn, String sql,Object... param);
// 到底选择哪一个API呢?
// 首先我们需要根据QueryRunner的构造方法,看一下我们去创建QueryRUnner的时候是否传入了数据库连接池
// 如果传入了数据库连接池,那么在后续使用API的时候,就可以不传入connection对象
// 如果我们去创建QueryRUnner对象的时候,使用的是无参构造方法,那么我们在执行sql的时候一定要传入connection对象
// 查询的API
queryRunner.query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)
queryRunner.query(String sql, ResultSetHandler<T> rsh, Object... params)
queryRunner.query(Connection conn, String sql, ResultSetHandler<T> rsh)
queryRunner.query(String sql, ResultSetHandler<T> rsh)
update:
public class DButilsUpdateMain {
public static void main(String[] args) throws SQLException {
// 获取连接
Connection connection = DruidUtils.getConnection();
// 创建一个QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
// 执行SQL语句
String sql = "update student set name = ? where id = ?";
// 返回的是影响的行数
int affectedRows = queryRunner.update(connection,sql,"段誉",10);
System.out.println("affectedRows:" + affectedRows);
// 解析结果集
// 关闭资源
connection.close();
}
}
2.3 ResultSetHandler
其实这个类就是一个结果集处理器。可以帮助我们把SQL语句查询的结果集对象(ResultSet)封装成Java对象
底层原理:反射
如果我们要使用ResultSetHandler接收结果,比如结果要封装为一个Student对象,一定要给Student类写好get/set方法,因为ResultSetHandler是通过反射来把结果封装成对象的。
我就是因为写错了set的名字,导致接收到的日期值一直是null,一开始没有认为是set的问题,因为并没有调用过set
2.3.1 BeanHandler
将结果集中的第一行数据封装到一个对应的JavaBean实例中。
public class BeanHandlerDemo {
// 实践一下BeanHandler
public static void main(String[] args) throws SQLException {
// 创建一个QueryRunner对象
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 执行SQL语句
Student student = queryRunner.query("select id,name,chinese,english,math,birthday,native_place as nativePlace from student where id = ?",new BeanHandler<>(Student.class),4);
System.out.println(student);
}
}
2.3.2 BeanListHandler
将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
// 这个Handler其实是把resultSet里面的每一行都转化为一个Java对象,最后给我们返回一个Java对象的集合
public class BeanListHandlerDemo {
public static void main(String[] args) throws SQLException {
// 创建QUeryRunner
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 执行sql
List<Student> studentList = queryRunner.query("select id,name,chinese,english,math,birthday,native_place as nativePlace from student", new BeanListHandler<Student>(Student.class));
// 输出
System.out.println(studentList);
}
}
2.3.3 ColumnListHandler
将结果集中某一列的数据存放到List中
// 把单列的值封装到一个list里面去
public class ColumnListHandlerDemo {
public static void main(String[] args) throws SQLException {
// 创建QueryRUnner对象
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
// 执行sql
List<String> nameList = queryRunner.query("select name from student", new ColumnListHandler<>());
System.out.println("nameList:" + nameList);
}
}
2.3.4 ScalarHandler
将单个值封装,可以用来统计聚合函数count(),max(),min(),avg()等方法返回的值
// 获取单个值
public class ScalarHandlerDemo {
public static void main(String[] args) throws SQLException {
// 创建QUeryRUnner
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
Long count = queryRunner.query("select count(id) from user", new ScalarHandler<>());
System.out.println("count:" + count);
}
}