数据库连接池
由于数据库的连接对象创建工作,比较消耗性能。
所以一开始先在内存中开辟一块空间(集合),先往池子里面放置多个连接对象。后面需要连接数据库的时候,直接从池子中获取,不需要自行创建连接对象。使用完毕后,归还连接对象,确保连接对象能循环利用。
所建的MyDateSource类要继承DataSource接口(Sun公司针对数据库连接的定义的一套规范),并实现其方法。目前只实现其中连接池对外公布的获取连接的方法getConnction()
自定义数据库连接池对象
具体代码示例如下:
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/*
*这是一个数据库连接池
* 一开始先往池子里面放10个连接(通过构造方法或者静态代码块方法实现)
*
* 1.开始创建10个连接
*
* 2.来的程序通过getConnection获取连接
*
* 3.用完之后,使用addBack归还连接
*
* 4.扩容
* */
public class MyDateSource implements DataSource {
List<Connection> list = new ArrayList<Connection>();//通过集合体现连接池。
public MyDateSource() throws SQLException {
for (int i =0;i<9;i++){
Connection conn = JDBCutil.getConn();
list.add(conn);
}
}
//该连接池对外公布的获取的连接方法
@Override
public Connection getConnection() throws SQLException {
//获取连接的时候,先检测是否还有空闲的连接
if (list.size()==0){//如果没有,扩容,重新创建5个新连接添加,分配
for (int i =0;i<5;i++){
Connection conn = JDBCutil.getConn();
list.add(conn);
}
}
//remove(0) ----> 移除第一个,移除集合中的第一个
Connection conn = list.remove(0);//借用一个连接就移除一个连接,返回值是所移除的对象
return conn;
}
//归还连接方法。
public void addBack(Connection conn){
list.add(conn);
}
}
不过这样基础的实现,会带来一些问题:比如
- 需要额外记住 addBack()方法
- 单例问题,如果new 多个 MydataSource对象,会导致出现对个连接池对象。
- 无法面向接口编程,因为Datasource接口中没有addBack()方法,编译时候会报错。(DataSource d = new MydataSource; //编译报错)
解决方法:(以addBack为切入点)
修改接口中的close方法,原来的Connection对象的close方法是真的关闭连接。而我们可以修改这个close方法,使其调用的时候,不再是关闭连接,而是归还连接对象。
使用装饰者模式的方法(面向接口的思想),也就是把原有的Connection类包装起来。即是,我们自己写一个类,继承Connection接口,然后将实现的Connection类作为构造函数参数传入类中,而后需要用到哪些Connection接口的方法,就调用传进来的Connection参数去调用其本身的方法,对于想改造的类,就先调用的自己的方法,再调用其参数本身的方法。通过这样的方式进行对源码方法的扩展。
如以下所示:需要改造的方法是close,用到的方法是prepareStatement
public class ConnectionWrap implements Connection {//包装类,包装connection实例
List<Connection> list;
Connection connection = null;
//构造方法,传入一个connection实例作为调用。
public ConnectionWrap(Connection connection, List<Connection> list){
super();
this.connection = connection;
this.list = list;
}
@Override
public void close() throws SQLException {
System.out.println("有人来归还对象了,归还之前是:" + list.size());
list.add(connection);
System.out.println("有人来归还对象了,归还之后是:" + list.size());
//connection.close();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return connection.prepareStatement(sql);
}
....
}
public class MyDataSource implements DataSource {//继承了DataSource接口的连接池类
List<Connection> list = new ArrayList<Connection>();//通过集合体现连接池。
public MyDataSource() throws SQLException {
for (int i =0;i<9;i++){
Connection conn = JDBCutil.getConn();
list.add(conn);
}
}
//该连接池对外公布的获取的连接方法
@Override
public Connection getConnection() throws SQLException {
//获取连接的时候,先检测是否还有空闲的连接
if (list.size()==0){//如果没有,扩容,重新创建5个新连接添加,分配
for (int i =0;i<5;i++){
Connection conn = JDBCutil.getConn();
list.add(conn);
}
}
//remove(0) ----> 移除第一个,移除集合中的第一个元素
Connection conn = list.remove(0);//借用一个连接就移除一个连接,返回值是所移除的对象
//在把这个对象抛出去的时候,对这个对象进行包装
Connection connection = new ConnectionWrap(conn,list);//经过包装类包装的connection类
return connection;
}
}
@Test
public void PoolTest() throws SQLException {//测试运行类
Connection conn = null;
PreparedStatement ps = null;
MyDataSource dataSource = new MyDataSource();
try {
conn = dataSource.getConnection();
String sql = "insert into bank values (null ,'kk','1000')";
ps = conn.prepareStatement(sql);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
/*ps.close();
dataSource.addBack(conn);//归还连接*/
//传入的是经过装饰过的connection对象,使用的是我们自己写的close方法。
JDBCutil.release(ps,conn);
}
}
开源数据库连接池对象一:DBCP
需要引入三个jar包,分别是:
commons-dbcp2-2.5.0,commons-pool2-2.6.0,commons-logging-1.2
而其中如果不导入 commons-logging-1.2 包可能会引起 java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory 错误,找不到相关类
直接代码写的方式:
public void testDBCP() throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.构建数据源对象
BasicDataSource dataSource = new BasicDataSource();
//连接的是什么类型的数据库,访问的是什么类型的数据库,用户名,密码
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
conn = dataSource.getConnection();
String sql = "insert into bank values (null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,"kk6");
ps.setDouble(2,1100);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCutil.release(ps,conn);
}
}
配置文件方式:dbcpconfig.properties 文件
#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username=root
password=root
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
使用方式:
import JDBCutil.JDBCutil;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Properties;
public class DBCPDemo02 {
@Test
public void DBCPtest02() throws Exception {
Connection conn = null;
PreparedStatement ps = null;
try {
BasicDataSourceFactory factory = new BasicDataSourceFactory();
Properties properties = new Properties();
//通过类加载器的方式加载properties配置文件。
InputStream is = DBCPDemo02.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
//InputStream is = new FileInputStream("dbcpconfig.properties");用于配置文件在项目根目录下,直接读取
properties.load(is);
DataSource dataSource = factory.createDataSource(properties);
conn = dataSource.getConnection();
String sql = "insert into bank values (null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,"kk7");
ps.setDouble(2,1500);
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCutil.release(ps,conn);
}
}
}
开源数据库连接池对象二:C3P0
需要引入两个jar包
c3p0-0.9.5.2.jar,mchange-commons-java-0.2.12.jar
不导入 mchange-commons-java-0.2.12.jar 包可能会引起 java.lang.NoClassDefFoundError: com/mchange/v2/ser/Indirector 错误,找不到相关类
直接代码写的方式:
import JDBCutil.JDBCutil;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class C3P0Demo {
@Test
public void C3P0test(){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.创建datasource
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//2.设置连接数据的信息
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
dataSource.setUser("root");
dataSource.setPassword("root");
conn = dataSource.getConnection();
String sql = "insert into bank values (null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,"kk8");
ps.setDouble(2,1530);
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCutil.release(ps,conn);
}
}
}
使用配置文件方式:
需要注意,配置文件的名字一定要是 c3p0-config.xml ,因为在框架源码中,会自动使用类加载器的方式加载为c3p0-config.xml的文件,所以在使用的时候,只需要new ComboPoolDataSource对象,然后使用即可,框架会自动帮我们加载配置文件。
同时在配置文件中<default-config>标签指的是:默认的数据库配置,new对象时没有指定名称则会默认使用此配置。
还能可以使用<name-config name=“oracle”> 标签来配置另一个数据库的连接,在new ComboPoolDataSource 对象时带入name参数可以使用指定配置,如:ComboPooledDataSource dataSource1 = new ComboPooledDataSource("oracle");
并且c3p0-config.xml 要跟随java文件,放置在src目录下,不然程序将读取不到配置文件。
配置文件:c3p0-config.xml
<c3p0-config>
<!--default-config指的是,默认的数据库配置,new对象时没有指定名称则会默认使用此配置-->
<!--可以使用 <name-config name="oracle"> 标签来配置另一个数据库的连接,在new ComboPoolDataSource对象时带入name作为参数指定使用配置-->
<default-config>
<property name="automaticTestTable">con_test</property>
<!--当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出 SQLException,如设为0则无限期等待。单位毫秒。Default: 0 -->
<property name="checkoutTimeout">30000</property>
<!-- 测试空闲连接的间隔时间 default: 0 -->
<property name="idleConnectionTestPeriod">30</property>
<!-- 初始化时连接数,default: 3 -->
<property name="initialPoolSize">10</property>
<!-- 连接的最大空闲时间,default: 0 -->
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">25</property>
<!-- 连接池中最小连接数,default: 3 -->
<property name="minPoolSize">5</property>
<!-- 连接池为数据源缓存的PreparedStatement的总数 -->
<property name="maxStatements">200</property>
<!--IDEA将“&”当成了特殊符号 不允许直接使用&,需要替换成 “&”-->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/kk?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false</property>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">root</property>
</default-config>
</c3p0-config>
代码:
package C3P0util;
import JDBCutil.JDBCutil;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class C3P0Demo02 {
@Test
public void C3P0test02(){
Connection conn = null;
PreparedStatement ps = null;
try {
//配置文件方式,直接new一个对象使用即可,默认会找xml中的 default-config 分支
//因为在源码中,自动使用了类加载器的方式加载名为:c3p0-config.xml 的文件,所以xml的名字需要固定为c3p0-config.xml
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//带入oracle参数,使用xml配置文件中名为的 oracle 分支
//ComboPooledDataSource dataSource1 = new ComboPooledDataSource("oracle");
conn = dataSource.getConnection();
String sql = "insert into bank values (null,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,"kk8");
ps.setDouble(2,1530);
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCutil.release(ps,conn);
}
}
}
数据库操作对象:DButils
需要导入DButils的jar包,commons-dbutils-1.7.jar
在DButils框架中,只是帮我们简化了CRUD的代码,也就是操作数据库语句代码,但是对于连接的创建和获取工作,并不负责,所以,需要将DButils 和 上面的连接池对象 C3P0,DBCP 相结合使用。
DBUtils的3个核心对象。QueryRunner类(里面有query查询、update增删改和batch批处理)、ResultSetHandler接口(用于select后如何封装数据的)、DBUtils类(定义了关闭资源和事务处理的方法)。
而DButils框架将数据库操作分为了两种方法,QueryRunner.update(增,删,改)和 QueryRunner.query(查)
update 方法针对 增,删,改语句;而 query 方法针对查询语句。
普通代码示例如下:
实体类Bank:
package BankModel;
public class Bank {
private String name;
private double money;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Bank{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
public void setMoney(double money) {
this.money = money;
}
public String getName() {
return name;
}
public double getMoney() {
return money;
}
}
DButilTest的update(增,删,改)部分代码如下:
package DButil;
import BankModel.Bank;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.Test;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DButilsTest {
@Test
public void InsertTest() throws SQLException {
//创建连接对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//dbutils 只是帮我们简化了CRUD的代码,但是连接的创建以及获取工作,不属于他的工作范围
QueryRunner queryRunner = new QueryRunner(dataSource);//传入一个连接对象作为参数
//update方法针对增加,删除,修改
//update(String sql,Object...params)
//增加
int i = queryRunner.update("insert into bank values(null,?,?)","kk9",1255);
//删除
int j = queryRunner.update("delete from bank where id=?",8);
//修改
int k = queryRunner.update("update bank set name=?,money=? where id=?","kk08",2988,9);
}
}
DButilTest的query(查询)部分代码(new 匿名实现类的实例化ResultSetHandler接口方法)如下:
package DButil;
import BankModel.Bank;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.Test;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DButilsTest {
@Test
public void InsertTest() throws SQLException {
//创建连接对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//dbutils 只是帮我们简化了CRUD的代码,但是连接的创建以及获取工作,不属于他的工作范围
QueryRunner queryRunner = new QueryRunner(dataSource);//传入一个连接对象作为参数
//query方法针对查询,需要准备一个封装返回数据的类对象,如Bank类
//query(String sql,ResultSetHandler<T>,Object...Params)
//去执行查询,查询到的数据还是存放在Result里,然后调用接口中的handle方法,由用户手动去封装返回的数据
Bank bank = queryRunner.query("select * from bank where id=?", new ResultSetHandler<Bank>() {
//通过匿名实现类的方式,实例化ResultSetHandler<T>接口,使得返回值为Bank实体类对象。
@Override
public Bank handle(ResultSet resultSet) throws SQLException {
Bank bank = new Bank();
while (resultSet.next()){
//自行接收数据,并且将数据封装在准备好的bank类对象中,返回这个对象。
String name = resultSet.getString("name");
double money = resultSet.getDouble("money");
bank.setMoney(money);
bank.setName(name);
}
return bank;
}
}, 9);
}
}
上面的query查询代码由于需要自己新建一个匿名实现类,过程复杂,所以需要进行简化,简化方法就是找到ResultSetHandler (结果集的处理接口)接口的具体实现类作为参数。
查询单个对象的实现类:new BeanHandler<T>(Class<T> type),例:new BeanHandler<Bank>(Bank.class)
查询多个对象的实现类(返回值是集合):new BeanListHandler<T>(Class<T> type),例:
new BeanListHandler<Bank>(Bank.class)
而上面的参数 Bank.class 的意思是:通过该类的字节码得到该类的实例(反射),也即是框架自动帮我们创建了类的实例作为参数
优化后的query(具体实现类方法)代码如下:
package DButil;
import BankModel.Bank;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.Test;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DButilsTest {
@Test
public void InsertTest() throws SQLException {
//创建连接对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//dbutils 只是帮我们简化了CRUD的代码,但是连接的创建以及获取工作,不属于他的工作范围
QueryRunner queryRunner = new QueryRunner(dataSource);//传入一个连接对象作为参数
//查询单个对象,返回值是一个对象的使用BeanHandler 类
//参数Bank.Class 通过类的字节码得到该类的实例(反射)
//Bank bank = Bank.class.newInstance();//创建一个类的实例
//new BeanHandler<T>(Class<T> type)
Bank bank = queryRunner.query("select * from bank where id=?",
new BeanHandler<Bank>(Bank.class),7);
System.out.println(bank.toString());
//查询多个对象,返回值是一个集合
//new BeanListHandler<T>(Class<T> type)
List<Bank> list = queryRunner.query("select * from bank",
new BeanListHandler<Bank>(Bank.class));
for (Bank bank1:list
) {
System.out.println(bank1.toString());
}
}
}
而ResultSetHandler(结果集的处理接口)的常用实现类有:
BeanHandler:查询到的单个数据封装成一个对象。
BeanListHandler:查询到的多个数据封装成一个List对象。
ArrayHandler:查询到的单个数据封装成一个数据。
ArrayListHandler:查询到的多个数据封装成一个集合,集合里面的元素是数组。
MapHandler:查询到的单个数据封装成一个map。
MapListHandler:查询到的多个数据封装成一个集合,集合里面的元素是map。
总结
数据连接池
负责与数据库的连接对象的创建和获取功能
DBCP(不使用配置文件的方法,使用配置文件的方法)
C3P0(不使用配置文件的方法,使用配置文件的方法)
自定义连接池(装饰者模式 Wrap)
DBUtils
简化了我们的CRUD,里面定义了通用的CRUD方法
queryRunner.update();
quertRunner.query();