1,自定义链接池
讲解
为什么要使⽤连接池
Connection
对象在
JDBC
使⽤的时候就会去创建⼀个对象,使
⽤结束以后就会将这个对象给销毁了
(close)
。每次创建和销毁
对象都是耗时操作,需要使⽤连接池对其进⾏优化。
程序初始化的时候,初始化多个连接,将多个连接放⼊到池
(
集合
)
中。每次获取的时候,都可以直接从连接池中进⾏获
取。使⽤结束以后,将连接归还到池中。
⽣活⾥⾯的连接池例⼦
⽼⽅式
:
下了地铁需要骑⻋,跑去⽣产⼀个,然后骑完之后,直接
把⻋销毁了
.
连接池⽅式 摩拜单⻋
:
骑之前, 有⼀个公司⽣产了很多的⾃⾏⻋,下了地铁需要
骑⻋,直接扫码使⽤就好了,然后骑完之后,还回去
连接池原理
1.
程序⼀开始就创建⼀定数量的连接,放在⼀个容器
(
集合
)
中,这个容器称为连接池。
2.
使⽤的时候直接从连接池中取⼀个已经创建好的连接对象
,
使⽤完成之后 归还到池⼦
3.
如果池⼦⾥⾯的连接使⽤完了
,
还有程序需要使⽤连接
,
先
等待⼀段时间
(eg: 3s),
如果在这段时间之内有连接归还
,
就
拿去使⽤
;
如果还没有连接归还
,
新创建⼀个
,
但是新创建的
这⼀个不会归还了
4.
集合选择
LinkedList
增删⽐较快
LinkedList
⾥⾯的
removeFirst()
和
addLast()
⽅法和连接
池的原理吻合
⼩结
使⽤连接池的⽬的: 可以让连接得到复⽤, 避免浪费
⾃定义连接池-初级版本
⽬标
根据连接池的原理
,
使⽤
LinkedList
⾃定义连接池
分析
1.
创建⼀个类
MyDataSource,
定义⼀个集合
LinkedList
2.
程序初始化的时候
,
创建
5
个连接 存到
LinkedList
3.
定义
getConnection()
从
LinkedList
取出
Connection
返回
4.
定义
addBack()
⽅法归还
Connection
到
LinkedList
实现
/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 09:37
* ⾃定义连接池的第⼀个版本
* 1. 创建⼀个容器,存放连接
* 2. 默认往容器中存放5个连接
* 在构造函数中编写代码
* 3. 提供⼀个⽅法,让调⽤者获取连接
* 4. 提供⼀个⽅法,让调⽤者归还连接
*
* 当前第⼀个版本存在的问题:
* 1. 新创建的连接(原本没有在连接池中的连接)也会归还回
连接池
* 2. 连接池使⽤的耦合性太⾼了,不便于以后项⽬切换连接池
*/
public class MyDataSource {
private LinkedList<Connection>
connectionPool = new LinkedList<>();
public MyDataSource() {
//初始化往connectionPool中存放5个连接
for (int i = 0; i < 5; i++) {
try {
//创建⼀个连接
Connection conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
//将连接添加到connectionPool中
connectionPool.add(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 获取连接的⽅法
* @return
*/
public Connection getConn() throws
SQLException {
//如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
Connection conn = null;
if (connectionPool.size() > 0){
//从容器中获取连接
conn =
connectionPool.removeFirst();
}else {
//则新创建连接
conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
}
return conn;
}
public void addBack(Connection connection){
//归还连接:其实就是将要归还的那个连接,添加到容
器的尾部
connectionPool.addLast(connection);
}
}
⼩结
1.
创建⼀个类
MyDataSource,
定义⼀个集合
LinkedList
2.
程序初始化
(
静态代码块
)
⾥⾯ 创建
5
个连接存到
LinkedList
3.
定义提供
Connection
的⽅法
4.
定义归还
Connection
的⽅法
⾃定义连接池-进阶版本
⽬标
实现datasource完成⾃定义连接池
分析
在初级版本版本中
,
我们定义的⽅法是
getConnection().
因为
是⾃定义的
.
如果改⽤李四的⾃定义的连接池
,
李四定义的⽅法
是
getAbc(),
那么我们的源码就需要修改
,
这样不⽅便维护
.
所
以
sun
公司定义了⼀个接⼝
datasource,
让⾃定义连接池有了
规范
讲解
datasource接⼝概述
Java
为数据库连接池提供了公共的接⼝:
javax.sql.DataSource
,各个⼚商
(
⽤户
)
需要让⾃⼰的连接池
实现这个接⼝。这样应⽤程序可以⽅便的切换不同⼚商的连接
池!
javax.sql.DataSource接口,中文翻译数据源,其实就是连接池。
从数据源中得到Connection连接对象。接口Sun公司没有具体的实现,
由各大数据库厂商去实现,很多第三方的公司也可以实现。
代码实现
/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 10:01
* ⾃定义连接池的第⼆版:
* 编写连接池,并且实现官⽅的DataSource接⼝
*
* 存在的问题:DataSource中并没有提供归还连接的⽅法
*/
public class MyDataSource2 implements
DataSource{
private LinkedList<Connection>
connectionPool = new LinkedList<>();
public MyDataSource2() {
//初始化往connectionPool中存放5个连接
for (int i = 0; i < 5; i++) {
try {
//创建⼀个连接
Connection conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
//将连接添加到connectionPool中
connectionPool.add(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public Connection getConnection() throws
SQLException {
//如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
Connection conn = null;
if (connectionPool.size() > 0){
//从容器中获取连接
conn =
connectionPool.removeFirst();
}else {
//则新创建连接
conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
}
return conn;
}
@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;
}
}
⼩结
编写连接池遇到的问题
实现
DataSource
接⼝后
,addBack()
不能调⽤了
.
能不能不引⼊新的
api,
直接调⽤之前的
connection.close(),
但是这个
close
不是关闭
,
是归还
解决办法
继承
条件
:
可以控制⽗类
,
最起码知道⽗类的名字
装饰者模式
作⽤:改写已存在的类的某个⽅法或某些⽅法
条件
:
增强类和被增强类实现的是同⼀个接⼝
增强类⾥⾯要拿到被增强类的引⽤
动态代理
⾃定义连接池-终极版本
⽬标
使⽤装饰者模式改写
connection
的
close()
⽅法
,
让
connection
归还
讲解
⾃定义连接池终极版本分析
增强connection的close()⽅法, 其它的⽅法逻辑不改
1.
创建
WrapperConnection
实现
Connection
2.
在
WrapperConnection
⾥⾯需要得到被增强的
connection
对象
(
通过构造⽅法传进去
)
3.
改写
close()
的逻辑
,
变成归还
4.
其它⽅法的逻辑
,
还是调⽤被增强
connection
对象之前的逻
辑
实现
WrapperConnection
/**
* 包名:com.sunlw.customer.connection
*
* @author sunlw
* ⽇期2020-07-06 10:16
* 依赖倒置原则:
* 尽量依赖抽象,不依赖具体
*/
public class WrapperConnection implements
Connection{
private Connection connection;
private LinkedList<Connection>
connectionPool;
public WrapperConnection(Connection
connection,LinkedList<Connection>
connectionPool) {
this.connection = connection;
this.connectionPool = connectionPool;
}
@Override
public void close() throws SQLException {
//将当前这个连接归还回原来那个连接池容器
connectionPool.addLast(this);
}
@Override
public Statement createStatement() throws
SQLException {
return connection.createStatement();
}
@Override
public PreparedStatement
prepareStatement(String sql) throws
SQLException {
return
connection.prepareStatement(sql);
}
@Override
public CallableStatement prepareCall(String
sql) throws SQLException {
return null;
}
@Override
public String nativeSQL(String sql) throws
SQLException {
return null;
}
@Override
public void setAutoCommit(boolean
autoCommit) throws SQLException {
connection.setAutoCommit(autoCommit);
}
@Override
public boolean getAutoCommit() throws
SQLException {
return false;
}
@Override
public void commit() throws SQLException {
connection.commit();
}
@Override
public void rollback() throws SQLException
{
connection.rollback();
}
//....其它⽅法省略
}
MyDataSource03
/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 10:01
* ⾃定义连接池的第三版:
* 1. 可以归还连接
* 2. 如果是原本在连接池中的连接,就归还;如果是新创建的
连接⽤完后就销毁
*
* 如果是原本在连接池中的连接,调⽤close()⽅法不销毁,
⽽是将其归还回连接池
* 如果是新创建的连接,调⽤close()⽅法,就销毁(执⾏原本
的close)
*
* 要在不修改类的源码的基础之上,改变类的⽅法
* 1. 继承(在这⾥没法⽤)
* 2. 动态代理(代理模式)
* 3. 装饰者模式
* 1. 装饰者和被装饰者要实现相同的接⼝
* 2. 将被装饰者的对象传⼊装饰者中
* 3. 不需要修改的⽅法,直接调⽤被装饰者的⽅法;需要
修改的⽅法,由装饰者重写
*/
public class MyDataSource3 implements
DataSource{
private LinkedList<Connection>
connectionPool = new LinkedList<>();
public MyDataSource3() {
//初始化往connectionPool中存放5个连接
for (int i = 0; i < 5; i++) {
try {
//创建⼀个装饰后的连接
Connection conn = new
WrapperConnection(DriverManager.getConnection("
jdbc:mysql:///day20?
characterEncoding=utf8","root","123"),connectio
nPool);
//将连接添加到connectionPool中
connectionPool.add(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public Connection getConnection() throws
SQLException {
//如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
Connection conn = null;
if (connectionPool.size() > 0){
//从容器中获取连接
conn =
connectionPool.removeFirst();
}else {
//则新创建连接
conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
}
return conn;
}
@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;
}
}
⼩结
1.
创建⼀个
MyConnection
实现
Connection
2.
在
MyConnection
得到被增强的
connection
对象
3.
改写
MyConnection
⾥⾯的
close()
⽅法的逻辑为归还
4. MyConnection
⾥⾯的其它⽅法 调⽤被增强的
connection
对象之前的逻辑
5.
在
MyDataSource03
的
getConnection()
⽅法⾥⾯ 返回了
myConnection
⾃定义连接池扩展版本-使⽤动态代理
使⽤动态代理创建
Connection
对象的代理对象,增强
Connection
的
close
⽅法
/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 10:01
* ⾃定义连接池的第四版:
* 1. 可以归还连接
* 2. 如果是原本在连接池中的连接,就归还;如果是新创建的
连接⽤完后就销毁
*
* 使⽤动态代理技术,增强connection的close⽅法
*/
public class MyDataSource4 implements
DataSource{
private LinkedList<Connection>
connectionPool = new LinkedList<>();
public MyDataSource4() {
//初始化往connectionPool中存放5个连接
for (int i = 0; i < 5; i++) {
try {
Connection connection =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
ClassLoader classLoader =
connection.getClass().getClassLoader();
//创建动态代理对象
Connection connectionProxy =
(Connection)
Proxy.newProxyInstance(classLoader, new Class[]
{Connection.class}, new InvocationHandler() {
@Override
public Object invoke(Object
proxy, Method method, Object[] args) throws
Throwable {
//判断执⾏⽅法是否是close⽅
法,如果是,则增强,如果不是则执⾏被代理者原本的⽅法
if
(method.getName().equals("close")) {
//增强close
//将当前这个连接对象,
添加到容器中
connectionPool.addLast((Connection) proxy);
return null;
}
//不需要增强的⽅法,就执⾏原
本的⽅法
return
method.invoke(connection,args);
}
});
//将连接添加到connectionPool中
connectionPool.add(connectionProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public Connection getConnection() throws
SQLException {
//如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
Connection conn = null;
if (connectionPool.size() > 0){
//从容器中获取连接
conn =
connectionPool.removeFirst();
}else {
//则新创建连接
conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
}
return conn;
}
@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;
}
}
第三⽅连接池
常⽤连接池
1.⽬标
常⽤连接池
2.分析
通过前⾯的学习,我们已经能够使⽤所学的基础知识构建⾃定
义的连接池了。其⽬的是锻炼⼤家的基本功,帮助⼤家更好的
理解连接池的原理
,
但现实是残酷的,我们所定义的 连接池 和
第三⽅的连接池相⽐,还是显得渺⼩
.
⼯作⾥⾯都会⽤第三⽅
连接池
.
3.讲解
常⻅的第三⽅连接池如下
:
C3P0
是⼀个开源的
JDBC
连接池,它实现了数据源和
JNDI
绑
定,⽀持
JDBC3
规范和
JDBC2
的标准扩展。
C3P0
是异步操
作的,所以⼀些操作时间过⻓的
JDBC
通过其它的辅助线程
完成。⽬前使⽤它的开源项⽬有
Hibernate
,
Spring
等。
C3P0
有⾃动回收空闲连接功能
阿⾥巴巴
-
德鲁伊
druid
连接池:
Druid
是阿⾥巴巴开源平台
上的⼀个项⽬,整个项⽬由数据库连接池、插件框架和
SQL
解析器组成。该项⽬主要是为了扩展
JDBC
的⼀些限
制,可以让程序员实现⼀些特殊的需求。
DBCP(DataBase Connection Pool)
数据库连接池,是
Apache
上的⼀个
Java
连接池项⽬,也是
Tomcat
使⽤的连
接池组件。
dbcp
没有⾃动回收空闲连接的功能。
4.⼩结
我们⼯作⾥⾯⽤的⽐较多的是
:
C3P0
druid
光连接池
C3P0使用
1.⽬标
掌握
C3P0
的使⽤
2.路径
1. c3p0
介绍
2. c3p0
的使⽤
(
硬编码
)
3. c3p0
的使⽤
(
配置⽂件
)
4.
编写
C3P0Util
⼯具类
3.讲解
c3p0介绍
C3P0
开源免费的连接池!⽬前使⽤它的开源项⽬有:
Spring
、
Hibernate
等。使⽤第三⽅⼯具需要导⼊
jar
包,
c3p0
使⽤时还需要添加配置⽂件
c3p0-config.xml.
使⽤
C3P0
需要添加
c3p0-0.9.1.2.jar
c3p0的使⽤
通过硬编码来编写
步骤
1.
拷⻉
jar
2.
创建
C3P0
连接池对象
3.
从
C3P0
连接池对象⾥⾯获得
connection
实现
:
ComboPooledDataSource cpds = new
ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/we
b10");
cpds.setUser("root");
cpds.setPassword("123456");
Connection connection = cpds.getConnection();
通过配置⽂件来编写
步骤
:
1.
拷⻉
jar
2.
拷⻉配置⽂件
(c3p0-config.xml)
到
src
⽬录【名字不要改】
3.
创建
C3P0
连接池对象【⾃动的读取】
4.
从池⼦⾥⾯获得连接
实现
:
编写配置⽂件
c3p0-config.xml
,放在
src
⽬录下(注:⽂
件名⼀定不要改
)
<c3p0-config>
<default-config>
<property
name="driverClass">com.mysql.jdbc.Driver</prope
rty>
<property
name="jdbcUrl">jdbc:mysql://localhost:3306/web1
1</property>
<property name="user">root</property>
<property name="password">123</property>
<property
name="initialPoolSize">5</property>
</default-config>
</c3p0-config>
编写
Java
代码
(
会⾃动读取
resources
⽬录下的
c3p0-
config.xml,
所以不需要我们解析配置⽂件
)
DataSource ds = new ComboPooledDataSource();
使⽤c3p0改写⼯具类
编写
C3P0Util
⼯具类,提供
DataSource
对象,保证整个项⽬
只有⼀个
DataSource
对象
/**
* 包名:com.sunlw.utils
*
* @author sunlw
* ⽇期2020-07-06 11:43
* 这个⼯具类就负责,提供C3P0连接池对象
*/
public class C3P0Util {
private static DataSource dataSource;
static {
dataSource = new
ComboPooledDataSource();
}
/**
* 获取连接池
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
}
⼩结
1.C3P0 配置⽂件⽅式使⽤
拷⻉jar
拷⻉配置⽂件到
resources
【配置⽂件的名字不要改】
创建
C3P0
连接池对象
2. C3P0
⼯具类
保证
DataSource
连接池只有⼀个【
static
】
知识点-DRUID
1.⽬标
掌握
DRUID
连接池的使⽤
2.路径
1. DRUID
的介绍
2. DRUID
的使⽤
(
硬编码⽅式
)
3. DRUID
的使⽤
(
配置⽂件⽅式
)
4. DRUID
抽取成⼯具类
3.讲解
DRUID介绍
Druid
是阿⾥巴巴开发的号称为监控⽽⽣的数据库连接池,
Druid
是国内⽬前最好的数据库连接池。在功能、性能、扩展
性⽅⾯,都超过其他数据库连接池。
Druid
已经在阿⾥巴巴部
署了超过
600
个应⽤,经过很久⽣产环境⼤规模部署的严苛考
验。如:⼀年⼀度的双⼗⼀活动,每年春运的抢⽕⻋票
Druid
的下载地址:
https://github.com/alibaba/druid
DRUID
连接池使⽤的
jar
包:
druid-1.0.9.jar
![](https://i-blog.csdnimg.cn/blog_migrate/92be98c3960f203e074162b5ad38cb47.png)
DRUID的使⽤
通过硬编码⽅式
步骤
:
1.
导⼊
DRUID jar
包
2.
创建
Druid
连接池对象
,
配置
4
个基本参数
3.
从
Druid
连接池对象获得
Connection
实现
:
//1. 创建DataSource
DruidDataSource dataSource = new
DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.D
river");//设置驱动
dataSource.setUrl("jdbc:mysql://localhost:3306/
web17");//设置数据库路径
dataSource.setUsername("root");//设置⽤户名
dataSource.setPassword("123");//设置密码
dataSource.setInitialSize(5);//设置初始化连接的数量
//2. 从数据源⾥⾯获得Connection
Connection connection =
dataSource.getConnection();
通过配置⽂件⽅式
步骤
:
1.
导⼊
DRUID jar
包
2.
拷⻉配置⽂件到
src
⽬录
3.
根据配置⽂件创建
Druid
连接池对象
4.
从
Druid
连接池对象获得
Connection
实现
:
创建
druid.properties,
放在
src
⽬录下
url=jdbc:mysql:///day20
username=root
password=123
driverClassName=com.mysql.jdbc.Driver
编写
Java
代码
//0 根据druid.properties创建配置⽂件对象
Properties properties = new Properties();
// 关联druid.properties⽂件
InputStream is =
DruidDemo.class.getClassLoader().getResourceAsS
tream("druid.properties");
properties.load(is);
//1. 创建DataSource
DataSource dataSource =
DruidDataSourceFactory.createDataSource(propert
ies);
//2. 从数据源(连接池)获得连接对象
Connection connection =
dataSource.getConnection();
Druid⼯具类
/**
* 包名:com.sunlw.utils
*
* @author sunlw
* ⽇期2020-07-06 11:45
*/
public class DruidUtil {
private static DataSource dataSource;
static {
try {
//1. 创建Properties对象
Properties properties = new
Properties();
//2. 将配置⽂件转换成字节输⼊流
InputStream is =
DruidUtil.class.getClassLoader().getResourceAsS
tream("druid.properties");
//3. 使⽤properties对象加载is
properties.load(is);
//druid底层是使⽤的⼯⼚设计模式,去加载配
置⽂件,创建DruidDataSource对象
dataSource =
DruidDataSourceFactory.createDataSource(propert
ies);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource(){
return dataSource;
}
}
⼩结
Druid
配置⽂件使⽤
拷⻉
jar
拷⻉配置⽂件到
src
读取配置⽂件成
properties
对象
使⽤⼯⼚根据
properties
创建
DataSource
从
DataSource
获得
Connection