day56上
数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高了数据库操作的性能。
理解:
数据库连接池就是容器,容器中存放了多个连接对象
使用原因:
1.优化创建和销毁连接的时间(在项目启动时创建连接池,项目销毁时关闭连接池)
2.提高连接对象的复用率
3.有效控制项目中连接的个数
自定义连接池
版本一:
写一个数据库连接池的类FastConnectionPool
定义成员变量,实例方法init()【将创建的线程池连接对象存放在集合里】
获取连接对象的方法getConnection()【判空,空就调实例方法创建,非空就从集合取,最后返回】
回收连接对象的方法recovery【将用完的连接对象加回集合】
public class FastConnectionPool {
private String driverClassName;
private String url;
private String username;
private String password;
private int maxActive;
private LinkedList<Connection> list;
//get()/set()
public void init() throws SQLException {
list = new LinkedList<>();
try {
Class.forName(driverClassName);
//创建连接对象,并添加到连接池容器中
for (int i = 0; i < maxActive; i++) {
Connection connection = DriverManager.getConnection(url, username, password);
list.add(connection);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public Connection getConnection() throws SQLException {
if(list == null){
init();
}
Connection connection = null;
if(!list.isEmpty()){
connection = list.removeFirst();
}
return connection;
}
public void recovery(Connection connection){
list.add(connection);
}
}
测试:
创建数据库连接池
设置参数【这里没有用配置文件】获取连接对象,获取指令发送对象,查询返回结果集,结果集遍历,关闭资源
public class Test01 {
/**
* 知识点:数据库连接池 之 自定义连接池
* 理解:池就是容器,容器中存放了多个连接对象
* 使用原因:
* 1.优化创建和销毁连接的时间(在项目启动时创建连接池,项目销毁时关闭连接池)
* 2.提高连接对象的复用率
* 3.有效控制项目中连接的个数
*/
public static void main(String[] args) throws SQLException {
//创建数据库连接池
FastConnectionPool pool = new FastConnectionPool();
//设置参数
pool.setDriverClassName("com.mysql.cj.jdbc.Driver");
pool.setUrl("jdbc:mysql://localhost:3306/2403javaee?characterEncoding=utf8&serverTimezone=UTC&rewriteBatchedStatements=true");
pool.setUsername("root");
pool.setPassword("root");
pool.setMaxActive(20);
Connection connection = pool.getConnection();
String sql = "select * from student";
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery();
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String sex = resultSet.getString("sex");
int age = resultSet.getInt("age");
float salary = resultSet.getFloat("salary");
String course = resultSet.getString("course");
System.out.println(id + " -- " + name + " -- " + sex + " -- " + age + " -- " + salary + " -- " + course);
}
resultSet.close();
statement.close();
pool.recovery(connection);
}
}
连接池规范
连接池各有各的实现方式,所以sun公司定义了一个标准,DataSource
所以上述我们实现的连接要符合sun公司的规范,就需要实现接口DataSource,所以我们可以编写一个MyDataSource类
之后就是面向Datasource来编程,但是会遇到问题,连接资源怎么关闭用到装饰设计模式(包装模式)
目的:改写已存在的类的某个方法或某些方法
使用过程,重点是要实现close方法
步骤:
1.class MyConnectionWrapper implements Connection,会实现很多方法【采取不需要改写的方法,调用原有的方法】,定义一个被包装类的类型对象和连接池容器,定义构造方法。
2.class FastConnectionPool implements DataSource,线程池的实现,主要是连接对象的获取存储和取出方法
3.测试使用,就区别在于用完连接后的处理
实现原则:对于不需要改写的方法,调用原有的方法;对于需要改写的方法,写自己的代码
版本二:
理解:主要就是通过实现方法改变close()把连接对象存回连接池。
/**
* 数据库连接的包装类
*/
public class MyConnectionWrapper implements Connection {
private Connection connection;
private LinkedList<Connection> list;//连接池里的容器
public MyConnectionWrapper(Connection connection, LinkedList<Connection> list) {
this.connection = connection;
this.list = list;
}
@Override
public Statement createStatement() throws SQLException {
return connection.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return connection.prepareStatement(sql);
}
......
@Override
public void close() throws SQLException {
System.out.println("将连接包装类对象回收到连接池里的容器");
list.add(this);
}
......
}
主要就是将连接对象包装了一下
MyConnectionWrapper connectionWrapper = new MyConnectionWrapper(connection, list);
public class FastConnectionPool implements DataSource {
private String driverClassName;
private String url;
private String username;
private String password;
private int maxActive;
private LinkedList<Connection> list;
//get()/set()
public void init() throws SQLException {
list = new LinkedList<>();
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < maxActive; i++) {
Connection connection = DriverManager.getConnection(url, username, password);
MyConnectionWrapper connectionWrapper = new MyConnectionWrapper(connection, list);
list.add(connectionWrapper);
}
}
@Override
public Connection getConnection() throws SQLException {
if(list == null){
init();
}
Connection connection = null;
if(!list.isEmpty()){
connection = list.removeFirst();
}
return connection;
}
......
}
测试没有变化,就close的意义不再是把连接对象关闭而是回收
public class Test01 {
public static void main(String[] args) throws SQLException {
//......
connection.close();
}
}
开源数据库连接池
通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
也有一些开源组织提供了数据源的独立实现:
· DBCP 数据库连接池【过时】
· C3P0 数据库连接池【过时】
· Druid(德鲁伊) 数据库连接池
它们都实现Java提供的DataSource接口(简称数据源)
在使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。
Druid(德鲁伊) 数据库连接池
导Druid的jar包,才可以使用
相对于前面测试基本操作没变
public class Test01 {
public static void main(String[] args) throws SQLException {
//创建连接池对象
DruidDataSource pool = new DruidDataSource();
......
connection.close();
}
}
Druid封装DBUtil
写一个学生类,对学生表操作
public class Student {}
测试
public class Test01 {
/**
* 知识点:数据库连接池 之 Druid封装DBUtil
*/
public static void main(String[] args) throws SQLException, InstantiationException, IllegalAccessException {
String sql = "select * from student";
List<Student> students = DBUtil.commonQueryList(Student.class, sql);
for (Student stu : students) {
System.out.println(stu);
}
}
}
封装
在原有基础上只改动newDruid连接池和获取连接对象
package com.qf.utils;
import com.alibaba.druid.pool.DruidDataSource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* 数据库工具类
*/
public class DBUtil {
private static DruidDataSource pool;
private static ThreadLocal<Connection> local;
static{
Properties properties = new Properties();
try {
properties.load(DBUtil.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
String driverClassName = properties.getProperty("driverClassName");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
int maxActive = Integer.parseInt(properties.getProperty("maxActive"));
//初始化数据库连接池
pool = new DruidDataSource();
//设置参数
pool.setDriverClassName(driverClassName);
pool.setUrl(url);
pool.setUsername(username);
pool.setPassword(password);
pool.setMaxActive(maxActive);
local = new ThreadLocal<>();
}
/**
* 获取连接对象
*/
public static Connection getConnection() throws SQLException {
Connection connection = local.get();//获取当前线程的Connection对象
if(connection == null){
connection = pool.getConnection();//获取数据库连接池里的连接对象
local.set(connection);//将Connection对象添加到local中
}
return connection;
}
/**
* 关闭资源
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection != null){
try {
if(connection.getAutoCommit()){
connection.close();
local.set(null);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* 开启事务
*/
public static void startTransaction() throws SQLException {
Connection connection = getConnection();
connection.setAutoCommit(false);
}
/**
* 提交事务
*/
public static void commit() throws SQLException {
Connection connection = local.get();
if(connection != null){
connection.commit();
connection.close();
local.set(null);
}
}
public static void rollback() throws SQLException {
Connection connection = local.get();
if(connection != null){
connection.rollback();
connection.close();
local.set(null);
}
}
/**
* 更新数据(添加、删除、修改)
*/
public static int commonUpdate(String sql,Object... params) throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = getConnection();
statement = connection.prepareStatement(sql);
paramHandler(statement,params);
int num = statement.executeUpdate();
return num;
}finally {
close(connection,statement,null);
}
}
/**
* 添加数据 - 主键回填(主键是int类型可以返回)
*/
public static int commonInsert(String sql,Object... params) throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
statement = connection.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
paramHandler(statement,params);
statement.executeUpdate();
resultSet = statement.getGeneratedKeys();
int primaryKey = 0;
if(resultSet.next()){
primaryKey = resultSet.getInt(1);
}
return primaryKey;
}finally {
close(connection,statement,resultSet);
}
}
/**
* 查询多个数据
*/
public static <T> List<T> commonQueryList(Class<T> clazz,String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
statement = connection.prepareStatement(sql);
paramHandler(statement,params);
resultSet = statement.executeQuery();
//获取表数据对象
ResultSetMetaData metaData = resultSet.getMetaData();
//获取字段个数
int count = metaData.getColumnCount();
List<T> list = new ArrayList<>();
while(resultSet.next()){
T t = clazz.newInstance();
//获取字段名及数据
for (int i = 1; i <= count; i++) {
String fieldName = metaData.getColumnName(i);
Object fieldVal = resultSet.getObject(fieldName);
setField(t,fieldName,fieldVal);
}
list.add(t);
}
return list;
} finally {
DBUtil.close(connection,statement,resultSet);
}
}
/**
* 查询单个数据
*/
public static <T> T commonQueryObj(Class<T> clazz,String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
statement = connection.prepareStatement(sql);
paramHandler(statement,params);
resultSet = statement.executeQuery();
//获取表数据对象
ResultSetMetaData metaData = resultSet.getMetaData();
//获取字段个数
int count = metaData.getColumnCount();
if(resultSet.next()){
T t = clazz.newInstance();
//获取字段名及数据
for (int i = 1; i <= count; i++) {
String fieldName = metaData.getColumnName(i);
Object fieldVal = resultSet.getObject(fieldName);
setField(t,fieldName,fieldVal);
}
return t;
}
} finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
/**
* 处理statement对象参数数据的处理器
*/
private static void paramHandler(PreparedStatement statement,Object... params) throws SQLException {
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1,params[i]);
}
}
/**
* 获取当前类及其父类的属性对象
* @param clazz class对象
* @param name 属性名
* @return 属性对象
*/
private static Field getField(Class<?> clazz,String name){
for(Class<?> c = clazz;c != null;c = c.getSuperclass()){
try {
Field field = c.getDeclaredField(name);
return field;
} catch (NoSuchFieldException e) {
} catch (SecurityException e) {
}
}
return null;
}
/**
* 设置对象中的属性
* @param obj 对象
* @param name 属性名
* @param value 属性值
*/
private static void setField(Object obj,String name,Object value){
Field field = getField(obj.getClass(), name);
if(field != null){
field.setAccessible(true);
try {
field.set(obj, value);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
拓展
德鲁伊数据库连接池可视化监控
德鲁伊数据库连接池,本身自带一个管理员监控程序,方便数据库管理员对数据源进行监控!
查看SQL的执行状况
//显示SQL信息,必须给druid数据库连接池设置
ds.setFilters(“stat”);
<!--连接池启用Web监控统计功能start -->
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<init-param>
<!-- 不允许清空统计数据 -->
<param-name>resetEnable</param-name>