自定义连接池、C3P0、Druid
一. 相关概念
1. 概念
其实就是装有连接的容器,使用连接的时候,可以从池子中获得连接,使用完之后归还给池子
2. 好处
(1)提升效率(减少了创建和销毁的时间开销)
(2)控制服务器内存的开销
3. 规范
(1)用池子管理连接,这样就可以重复使用
(2)不是自己创建连接,而是通过池子获取连接
(3)使用完连接之后,调用连接的close()方法,其实不是关闭连接,而是把连接归还给池子
(4)连接池技术,可以完成连接的再次利用
二. 自定义连接池
1. 步骤
① 定义一个类,实现 DataSource 接口。
② 定义一个容器,用于保存多个 Connection 连接对象。
③ 定义静态代码块,通过 JDBC 工具类获取 10 个连接保存到容器中。
④ 重写 getConnection 方法,从容器中获取一个连接并返回。
⑤ 定义 getSize 方法,用于获取容器的大小并返回 --只是用于测试的,可以不写
2. 实现
public class MyDataSource implements DataSource {
//1.准备一个容器。用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
//2.定义静态代码块,获取多个连接对象保存到容器中
static{
for(int i = 1; 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.提供一个获取连接池大小的方法
public int getSize() {
return pool.size();
}
//其他方法不需要动
}
3. 最简单的归还连接
(1)在MyDataSource中添加一个归还连接的方法
public void back(Connection conn){
pool.add(conn);
}
(2)在测试类中,使用MyDataSource中定义的方法进行归还
//释放资源
rs.close();
pst.close();
//con.close(); // 用完以后,关闭连接
dataSource.back(con);//归还连接
(3)弊端:
新增的back方法并不是连接池规范中的规范,不同的连接池对该方法的定义会存在不同,而且这种归还方式存在破坏jdbc操作步骤的问题,代码侵入性太强。
(4)解决:
遵循原本jdbc的操作方式,调用con.close()方法,但是此时该方法不再是释放资源,而是归还连接,此时就需要对con对象的close功能进行增强
三. 归还连接方式
1. 继承方式(不可行)
(1)步骤:
a) 定义一个类,继承JDBC4Connection
b) 定义Connection连接对象和容器对象的成员变量
c) 通过有参构造方法为成员变量赋值
d) 重写close方法,完成归还连接
(2)弊端
因为DataSource原本返回的就是一个Connection对象,如果强转的话,就会出现子类的引用指向父类对象,这种方式不可行
2. 装饰设计模式
(1)概念
在不改变现有对象原有结构的情况下,动态的给该对象增加一些功能(对原有类进行一个包装)
(2)前提
装饰类必须要实现和被装饰类相同的接口
(3)实现
a) 定义一个类,实现和被装饰类相同的接口
b) 在类中声明一个原有对象的成员变量
c) 通过有参构造方法为其赋值
d) 重写方法,不想改进的方法继续调用原有对象中的功能,想改进的方法自己重写
(4)缺点
如果接口/父类中的抽象方法过多,会造成编写麻烦
(5)解决
适配器设计模式
(6)代码:
// 自定义连接池
public class MyDataSource implements DataSource {
//1.准备一个容器。用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
//2.定义静态代码块,获取多个连接对象保存到容器中
static {
for (int i = 1; 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);
MyDatasourceDecorator mycon = new MyDatasourceDecorator(con, pool);
return mycon;
} else {
throw new RuntimeException("连接数量已用尽");
}
}
//4.提供一个获取连接池大小的方法
public int getSize() {
return pool.size();
}
// 后面是实现接口的其他方法
.
.
}
// 装饰者类
public class MyDatasourceDecorator implements Connection {
private Connection con;
List<Connection> pool;
public MyDatasourceDecorator(Connection con, List<Connection> pool) {
this.con = con;
this.pool = pool;
}
@Override
public void close() throws SQLException {
pool.add(con);
}
// 后面是实现接口的其他方法
.
.
}
// 测试类
public class MyDataSourceTest {
public static void main(String[] args) throws Exception{
//1.创建连接池对象
MyDataSource dataSource = new MyDataSource();
System.out.println("使用之前的数量:" + dataSource.getSize());
//2.通过连接池对象获取连接对象
Connection con = dataSource.getConnection();
System.out.println(con.getClass());
//3.查询学生表的全部信息
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
//4.执行sql语句,接收结果集
ResultSet rs = pst.executeQuery();
//5.处理结果集
while(rs.next()) {
System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday"));
}
//6.释放资源
rs.close();
pst.close();
con.close(); // 用完以后,关闭连接
System.out.println("使用之后的数量:" + dataSource.getSize());
}
}
3. 适配器模式
(1)概念
用来解决接口和实现类之间的矛盾问题,是接口和实现类之间的桥梁,可以帮助简化代码,提高复用性(可以针对不同的功能类来进行适配)
(2)实现
a) 定义一个适配器类,实现对应的接口
b) 重写所有的抽象方法
c) 让功能类继承适配器类,重写自己需要的方法
d) 为了避免其他类来创建适配器类的对象,改适配器类需要定义成抽象类!
(3)弊端
接口中的方法还是要在适配器中在写一遍
(4)解决
动态代理
(5)扩展:
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
(6)代码
// 适配器类
public abstract class MyAdapter implements Connection {
private Connection con;
public MyAdapter(Connection con) {
this.con = con;
}
@Override
public void close() throws SQLException {
}
// 实现接口的其他方法(此处只写了这儿要用的方法)
.
.
}
// 适配器类实现类
public class MyAdapterExtends extends MyAdapter {
private Connection con;
List<Connection> pool;
public MyAdapterExtends(Connection con, List<Connection> pool) {
super(con);
this.con = con;
this.pool = pool;
}
@Override
public void close() throws SQLException {
pool.add(con);
}
}
// 自定义连接池类
public class MyDataSource implements DataSource {
//1.准备一个容器。用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
//2.定义静态代码块,获取多个连接对象保存到容器中
static {
for (int i = 1; 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);
MyAdapterExtends mycon = new MyAdapterExtends(con, pool);
return mycon;
} else {
throw new RuntimeException("连接数量已用尽");
}
}
//4.提供一个获取连接池大小的方法
public int getSize() {
return pool.size();
}
// 实现接口的其他方法
.
.
}
// 测试类
public class MyDataSourceTest {
public static void main(String[] args) throws Exception{
//1.创建连接池对象
MyDataSource dataSource = new MyDataSource();
System.out.println("使用之前的数量:" + dataSource.getSize());
//2.通过连接池对象获取连接对象
Connection con = dataSource.getConnection();
System.out.println(con.getClass());
//3.查询学生表的全部信息
String sql = "SELECT * FROM student";
PreparedStatement pst = con.prepareStatement(sql);
//4.执行sql语句,接收结果集
ResultSet rs = pst.executeQuery();
//5.处理结果集
while(rs.next()) {
System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday"));
}
System.out.println("释放之前的数量:" + dataSource.getSize());
//6.释放资源
rs.close();
pst.close();
con.close(); // 用完以后,关闭连接
System.out.println("使用之后的数量:" + dataSource.getSize());
}
}
4. 动态代理
(1)概念
在不改变目标对象方法的情况下对方法进行增强
被代理对象:真实的对象
代理对象:内存中的一个对象
(2)前提
代理对象必须和被代理对象实现相同的接口
(3)实现
//返回代理对象的方法
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
// ClassLoader loade:传被代理对象的类加载器,保证和被代理对象使用相同的类加载器
// Class<?>[] interfaces:传被代理对象实现的接口,保证和被代理对象使用相同接口
// InvocationHandler: 动态代理的处理方法,是一个接口,需要对方法怎么处理就写在这里面
// 调用被代理对象中所有的方法都会经过invoke方法,在里面拿到method方法对象,获取方法
// 的名字进行判断,如果是要增强的方法,则对其增强,如果不是,调用原有方法即可
//动态代理的处理方法(调用被代理对象中所有的方法都会经过invoke方法)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("eat".equals(method.getName())){
String arg = (String)args[0];
String str = "红烧排骨" + arg;
method.invoke(stu,str);
return null;
} else if(method.getName().equals("study")) {
System.out.println("来黑马学习");
return null;
}else {
return method.invoke(stu,args);
}
}
(4)参数说明:
Object proxy:就是当前的代理对象,基本不用
Method method:当前调用的方法的反射对象形式,比如调用的是eat,那么这个method就是反射中eat的方法对象;调用的是study,那么这个method就是反射中study的方法对象
Object[] args:当前调用的方法的实际参数,比如调用的是eat方法,那么这个args数组中有一个元素,是米饭,调用的是study方法,那么这个args数组中没有元素
(5)实现归还连接代码
public class MyDataSource implements DataSource {
//1.准备一个容器。用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
//2.定义静态代码块,获取多个连接对象保存到容器中
static {
for (int i = 1; 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);
Connection mycon = (Connection)Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if ("close".equals(method.getName())) {
pool.add(con);
return null;
} else {
return method.invoke(con, objects);
}
}
});
return mycon;
} else {
throw new RuntimeException("连接数量已用尽");
}
}
//4.提供一个获取连接池大小的方法
public int getSize() {
return pool.size();
}
}
四. 第三方连接池
1. C3P0
(1)步骤
① 导入 jar 包。
② 导入配置文件到 src 目录下。
③ 创建 C3P0 连接池对象。
④ 获取数据库连接进行使用。
(2)实现
DataSource ds = new ComboPooledDataSource();
Connection conn = ds.getConnection();
(3)注意
c3p0是自动加载配置文件的,文件名和位置是固定(固定放在src目录下)
2. Druid
(1)步骤
① 导入 jar 包。
② 编写配置文件,放在 src 目录下。
③ 通过 Properties 集合加载配置文件。
④ 通过 Druid 连接池工厂类获取数据库连接池对象。
⑤ 获取数据库连接进行使用。
(2)实现
//获取配置文件的流对象
InputStream is = ClassLoader.getSystemResourceAsStream("druid.properties");
// 也可以通过线程获取流
// InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("druid.properties");
//1.通过Properties集合,加载配置文件
Properties prop = new Properties();
prop.load(is);
//2.通过Druid连接池工厂类获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//3.通过连接池对象获取数据库连接进行使用
Connection con = dataSource.getConnection();
(3)注意
Druid 不会自动加载配置文件,需要我们手动加载,但是文件的名称可以自定义。
五. 连接池工具类
1. 目的
连接池比连接更耗资源,确保在程序运行过程中连接池只创建一次
2. 步骤
(1)删除原工具类中注册驱动的代码
(2)拷贝创建连接的代码到静态代码块中
(3)重写获取连接的方法
(4)提供一个获取连接池的方法
(5)main方法中通过工具类来获得连接