数据库连接池
-
数据库连接池
- 数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现在的数据库连接,而不是再重新建立一个. 这项技术能明显提高对数据库操作的性能
-
DataSource接口概述
- javax.sql.DataSource接口: 数据源(数据库连接池). Java官方提供的数据库连接池规范(接口)
- 如果想完成数据库连接池技术, 就必须实现DataSource接口
- 核心功能: 获取数据库连接对象: Connection getConnection()
-
自定义数据库连接池
- 定义一个类, 实现DataSource接口
- 定义一个容器, 用于保存多个Connection连接对象
- 定义静态代码块, 通过JDBC工具类获取10个连接保存到容器中
- 重写getConnection方法, 从容器中获取一个连接并返回
- 定义getSize方法, 用于获取窗口的大小并返回
package com.test001;
import com.lizicai.utils.JDBCUtils;
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.Collections;
import java.util.List;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
// 1. 准备窗口, 用于保存多个连接对象, 变成线程安全的
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
// 2. 定义代码块, 通过工具类获取10个连接对象
static {
for(int i=0;i<10;i++){
Connection con = JDBCUtils.getConnection();
pool.add(con);
}
}
//3 重写getConnection() , 用于获取一个连接对象
@Override
public Connection getConnection() throws SQLException {
if( pool.size()> 0){
Connection con = pool.remove(0);
return con;
} else {
throw new RuntimeException("连接数量已用尽");
}
}
// 4. 定义getSize方法, 获取连接池窗口的大小
public int getSize(){
return pool.size();
}
}
package com.test001;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class MyDataSourceTest {
public static void main(String[] args) throws SQLException {
MyDataSource pool = new MyDataSource();
System.out.println(pool.getSize());
Connection con = pool.getConnection();
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while (rs.next()){
System.out.println(rs.getInt("sid")+"\t"+rs.getString("NAME")
+"\t"+rs.getInt("age")+"\t"+rs.getDate("birthday"));
}
rs.close();
pst.close();
con.close();
System.out.println(pool.getSize());
}
}
归还方式
- 归还数据库连接的方式
- 继承方式
- 装饰设计模式
- 我在适配器设计模式
- 动态 代理方式
归还方式 - 继承方式
- 继承方式归还数据库连接的思想。
- 通过打印连接对象,发现 DriverManager 获取的连接实现类是 JDBC4Connection
- 那我们就可以自定义一个类,继承JDBC4Connection这个类,重写closeQ 方法,完成连接对象的归还
- 继承方式归还数据库连接的实现步骤。
- 定义一个类,继承JDBC4Connection。
- 定义 Connection 连接对象和连接池容器对象的成员变量。
- 通过有参构造方法完成对成员变量的赋值。
- 重写cose 方法,将连接对象添加到池中。
- 继承方式日还数据库连接存任的问题。
- 通过查看 JDBC 工具类获取连接的方法发现:我们星然白定义了一个子类,完成了归还连接的操作。但是
- DriverManager 获取的还是JDBC4Connection这个对象,并不是我们的子类对象,而我们又不能整体去修改驱动包中类的功能,所继承这种方式行不通!
归还连接 - 装饰设计模式
- 装饰设计模式日还数据库连接的思想。
- 我们可以自定义一个类,实现 Connection接口。这样就具备了和 JDBC4Connection相同的行为了
- 重写close()方法,完成连接的归还。其余的功能还调用 mysql驱动包实现类原有的方法即可
- 装饰设计模式归还数据库连接的实现步骤。
- 定义一个类,实现 Connection 接口
- 定义 Connection 连接对象和连接池容器对象的成员变量
- 通过有参构造方法完成对成员变量的赋值
- 重写close()方法,将连接对象添加到池中
- 剩余方法,只需要调用mysql驱动包的连接对象完成即可
- 在自定义连接池中,将获取的连接对象通过自定义连接对象进行包装
- 装饰设计模式归还数据库连接存在的问题。
- 实现 Connection接口后,有大量的方法需要在自定义类中进行重写
package com.test002;
import java.sql.*;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 1. 定义一个类, 实现Connection接口
* 2. 定义连接对象和连接池容器对象的成员变量
* 3. 通过有参构造方法为成员变量赋值
* 4. 重写close()方法, 完成归还连接
* 5. 剩余方法, 还是调用原有的连接对象中的功能即可
*/
public class MyConnection2 implements Connection {
private Connection con;
private List<Connection> pool;
public MyConnection2(Connection con, List<Connection> pool) {
this.con = con;
this.pool = pool;
}
@Override
public void close() throws SQLException {
pool.add(con);
}
}
public class MyDataSource implements DataSource {
//3 重写getConnection() , 用于获取一个连接对象
@Override
public Connection getConnection() throws SQLException {
if( pool.size()> 0){
Connection con = pool.remove(0);
MyConnection2 myCon = new MyConnection2(con,pool);
return myCon;
} else {
throw new RuntimeException("连接数量已用尽");
}
}
}
归还连接 - 适配器设计模式
- 适配器设计模式归还数据库连接的思想。
- 我们可以提供一个适配器类,实现 Connection 接口,将所有方法进行实现(除了close方法)
- 自定义连接类只需要继承这个适配器类,重写需要改进的close0 方法即可
- 适配器设计模式归还数据库连接的实现步骤。
- 定义一个适配器类, 实现 Connection 接口。
- 定义 Connection 连接对象的成员变量。
- 通过有参构造方法完成对成员变量的赋值。
- 重写所有方法(除了close), 调用mysq驱动包的连接对象完成即可。
- 定义一个连接类,继承适配器类。
- 定义 Connection 连接对象和连接池容器对象的成员变量,并通过有参构造进行赋值。
- 重写close()方法,完成归还连接。
- 在自定义连接池中,将获取的连接对象通过自定义连接对象进行包装。
- 适配器设计模式归还数据库连接存在的问题。
- 自定义连接类虽然很简洁, 但我在楼下器还是自己编写的, 也比较麻烦
public abstract class MyAdapter implements Connection {
private Connection con;
public MyAdapter(Connection con){
this.con = con;
}
//省略其他实现的方法
}
package com.test002;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class MyConnection3 extends MyAdapter{
private Connection con;
private List<Connection> pool;
public MyConnection3(Connection con, List<Connection> pool) {
super(con);
this.con = con;
this.pool = pool;
}
@Override
public void close() throws SQLException {
pool.add(con);
}
}
动态代理
- 动态代理: 在不改变目标对象方法的情况下对方法进行增强
- 组成
- 被代理对象: 真实的对象
- 代理对象: 内存的一个对象
- 要求
- 代理对象必须和被代理对象实现相同的接口
- 实现
- Proxy.newProxyInstance()
package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
Student stu = new Student();
stu.eat("饭");
stu.study();
/**
* 要求: 在不改Student类中任何代码前提下, 通过study方法输出一句话
* 类加载器: 和被代理对象使用相同的类加载器
* 接口类型Class数组: 和被代理对象使用相同接口
* 代理规则: 完成代理增加的功能
*/
StudentInterface proxyStu = (StudentInterface) Proxy.newProxyInstance(stu.getClass().getClassLoader(), new Class[]{StudentInterface.class}, new InvocationHandler() {
/**
* 执行student类中所有方法都会经过invoke方法
* 对method方法进行判断
* 如果是study, 则对其增强
* 如果不是, 还调用学生对象原的功能即可
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("study")){
System.out.println("学快点");
return null;
} else {
return method.invoke(stu, args);
}
}
});
proxyStu.eat("米饭");
proxyStu.study();
}
}
归还连接 - 动态代理方式
- 动态代理方式归还数据库连接的思想。
- 我们可以通过 Proxy 来完成对 Connection 实现类对象的代理
- 代理过程中判断如果执行的是 close 方法,就将连接归还池中。如果是其他方法则调用连接对象原来 的功能即可
- 动态代理方式归还数据库连接的实现步骤。
- 定义一个类,实现 DataSource接口
- 定义一个容器,用于保存多个Connection连接对象
- 定义静态代码块,通过JDBC工具类获取 10 个连接保存到容器中
- 重马getConnection 方法,从容器中获取一个连接
- 通过 Proxy 代理,如果是close 方法,就将连接归还池中。如果是其他方法则调用原有功能
- 定义 getsize 方法,用于获取容器的大小并返回
- 动态代理方式归还数据库连接存在的问题。
- 我们自己写的连接池技术不够完善,功能也不够强大
package com.test001;
import com.lizicai.utils.JDBCUtils;
import com.test002.MyConnection2;
import com.test002.MyConnection3;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
// 1. 准备窗口, 用于保存多个连接对象, 更改List为线程安全的
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
// 2. 定义代码块, 通过工具类获取10个连接对象
static {
for(int i=0;i<10;i++){
Connection con = JDBCUtils.getConnection();
pool.add(con);
}
}
// 动态代理归还Connection
@Override
public Connection getConnection() throws SQLException {
if( pool.size()> 0){
Connection con = pool.remove(0);
Connection proxyCon =(Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
/** 执行Connection 实现类连接对象所有的方法都会经过invoke
* 如果是close方法, 归还连接
* 如果不是, 直接执行连接对象原有的功能即可
*
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("close")){
pool.add(con);
return null;
} else {
return method.invoke(con, args);
}
}
});
return proxyCon;
} else {
throw new RuntimeException("连接数量已用尽");
}
}
// 4. 定义getSize方法, 获取连接池窗口的大小
public int getSize(){
return pool.size();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
使用C3P0数据库连接池的使用步骤
- 导入Jar包, c3p0-0.9.5.5.jar和mchange-commons-java-0.2.19.jar
- 导入配置文件到src目录下
- 创建C3P0连接池对象
- 获取数据库连接进行使用
C3P0下载, 开头/c3p0-bin/c3p0-0.9.5.5/c3p0-0.9.5.5.bin.zip
<?xml version="1.0" standalone="no" ?>
<!--
<!DOCTYPE c3p0-config [
<!ENTITY extra SYSTEM "https://www.mchange.com/projects/c3p0/extra.xml">
]>-->
<c3p0-config>
<default-config>
<!-- <property name="automaticTestTable">con_test</property> -->
<property name="checkoutTimeout">30000</property>
<!-- <property name="idleConnectionTestPeriod">30</property> -->
<property name="initialPoolSize">10</property>
<!-- <property name="maxIdleTime">30</property> -->
<!-- <property name="maxIdleTimeExcessConnections">10</property> -->
<!-- <property name="maxConnectionAge">60</property> -->
<!-- <property name="propertyCycle">1</property> -->
<property name="maxPoolSize">5</property>
<property name="minPoolSize">2</property>
<!-- <property name="maxStatements">0</property> -->
<!-- <property name="maxStatementsPerConnection">5</property> -->
<!-- <property name="maxAdministrativeTaskTime">4</property> -->
<!-- <property name="connectionCustomizerClassName">com.mchange.v2.c3p0.test.TestConnectionCustomizer</property> -->
<!-- <property name="unreturnedConnectionTimeout">15</property> -->
<!-- <property name="debugUnreturnedConnectionStackTraces">true</property> -->
<!-- <property name="dataSourceName">poop</property> -->
<property name="driverClass">org.mariadb.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mariadb://192.168.0.100:3306/db14</property>
<property name="user">root</property>
<property name="password">rootPassword</property>
<extensions>
<!-- <property name="myXmlKey">myXmlVal</property> -->
<!-- <property name="myKey">myOverriddenVal</property> -->
</extensions>
<user-overrides user="swaldman">
<!--
<property name="unreturnedConnectionTimeout">5</property>
<property name="debugUnreturnedConnectionStackTraces">true</property>
-->
<!-- <property name="preferredTestQuery">select poop from doop</property> --><!-- intentionally broken -->
</user-overrides>
<!-- &extra;-->
</default-config>
<!--
<named-config name="dumbTestConfig">
<property name="maxStatements">200</property>
<property name="jdbcUrl">jdbc:test</property>
<property name="dataSourceName">scoop</property>
<user-overrides user="poop">
<property name="maxStatements">300</property>
</user-overrides>
</named-config>
-->
</c3p0-config>
使用默认的配置拿到连接, 可以正常使用了
DataSource dataSource = new ComboPooledDataSource();
Connection con = dataSource.getConnection();
package com.test003;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class C3P0Test2 {
public static void main(String[] args) throws SQLException {
DataSource dataSource = new ComboPooledDataSource();
for(int i=1;i<=11;i++){
Connection con = dataSource.getConnection();
System.out.println(i+":"+con);
// if( 5 == i){
// con.close();
// }
}
}
}
Druid连接池的使用
- Druid 数据库连接池的使用步骡。
- 导入jar包, druid-1.2.8.jar
- 编写配置文件,放在src目录下。
- 通过 Properties 集合加载配置文件。
- 通过 Druid 连接池工厂类获取数据库连接池对象。
- 获取数据库连接进行使用。
Druid不会自动加载配置文件,需要我们手动加载,但是文样的名称可以自定义
driverClassName=org.mariadb.jdbc.Driver
url=jdbc:mariadb://192.168.0.100:3306/db14
username=root
password=rootPassword
initialSize=5
maxActive=10
maxWait=3000
package com.test004;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
public class DruidTest1 {
public static void main(String[] args) throws Exception {
// 获取配置文件的流对象
InputStream is = DruidTest1.class.getClassLoader().getResourceAsStream("druid.properties");
// 1. 通过 Properties 集合, 加载配置文件
Properties prop = new Properties();
prop.load(is);
// 2. 通过Druid连接池工厂类获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
// 3. 通过连接池对象获取数据库进行使用
Connection con = dataSource.getConnection();
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while (rs.next()){
System.out.println(rs.getInt("sid")+"\t"+rs.getString("NAME")
+"\t"+rs.getInt("age")+"\t"+rs.getDate("birthday"));
}
rs.close();
pst.close();
con.close();
}
}
创建Druid 工具类
package com.lizicai.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 数据库连接池的工具类
*/
public class DataSourceUtils {
// 1. 私有构造方法
private DataSourceUtils(){}
// 2. 声明数据源变量
private static DataSource dataSource;
// 3. 提供静态代码块, 完成配置文件加载和获取数据库连接池对象
static {
try {
InputStream is = DataSourceUtils.class.getClassLoader().getResourceAsStream("druid.properties");
Properties prop = new Properties();
prop.load(is);
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
// 4. 提供一个获取数据库连接的方法
public static Connection getConnection() {
Connection con = null;
try {
con = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
// 5. 提供一个获取数据库连接池对象的方法
public static DataSource getDataSource() throws Exception {
return dataSource;
}
// 6. 释放资源的方法
public static void close(Connection con, Statement stat, ResultSet rs){
if(con != null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat != null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement stat){
if(con != null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat != null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
package com.test004;
import com.lizicai.utils.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DruidTest2 {
public static void main(String[] args) throws SQLException {
Connection con = DataSourceUtils.getConnection();
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while (rs.next()){
System.out.println(rs.getInt("sid")+"\t"+rs.getString("NAME")
+"\t"+rs.getInt("age")+"\t"+rs.getDate("birthday"));
}
DataSourceUtils.close(con,pst,rs);
}
}