JDBC—连接池
连接池的概念和作用
概念:概念:连接池的本质就是一个容器,该容器中会初始化一些Connection对象,我们程序只需要从连接池中获取连接,使用完毕之后归还连接即可。
作用:大大减少了频繁的创建和释放连接的时间,提高数据库操作的效率
连接池对程序带来的差异对比:
未使用连接池时,每来一个对数据库的访问就会创建一个连接,使用完了就关闭连接,这样导致当有大量的数据访问时,会频繁地创建和关闭连接,非常耗时。而在使用连接池之后,会提前准备一些数据连接,使用的时候就从池中获取,用完后重新归还给池中,可反复获取调用。
c3p0连接池的使用
使用步骤:
【前提】导入c3p0的依赖jar包(c3p0-0.9.5.2.jar)
【第1步】将c3p0-config.xml配置文件复制到src目录下(位置和名称都固定的)
【第2步】创建c3p0连接池对象(ComboPooledDataSource)
【第3步】通过连接池对象获得连接对象
【第4步】同JDBC操作数据库执行步骤,获取SQL执行对象、执行SQL语句。。。。
注意:
如果连接对象是从连接池中获取的,那么连接的close方法就不是关闭而是归还。
c3p0-config.xml的配置文件的名称和位置是固定的,配置文件放在src目录中,配置文件中的属性名也是固定的,否则无法根据属性名或者对应的属性值。
将properties文件作为配置文件【补充】
要求:属性文件的名称必须是c3p0.properties,必须放在src路径下,文件中的key是固定的必须以c3p0.开头。
druid连接池的使用【重点】🚩
使用步骤:
【前提】导入的druid依赖jar包(druid-1.0.9.jar)
【第1步】在src目录下创建配置文件,名称可以任意,一般叫做druid.properties
【第2步】使用Properties对象加载配置文件
【第3步】使用DruidDataSourceFactory.createDataSource()工厂创建连接池对象
【第4步】通过连接池对象获得连接对象
【第5步】同JDBC操作数据库执行步骤,获取SQL执行对象、执行SQL语句。。。。
自定义配置文件的内容
# 基础连接参数,名称是固定
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db14
username=root
password=root
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 等待超时时间
maxWait=3000复制
代码实现
方式1:直接创建DruidDataSource连接池对象,硬编码,暂时不推荐使用
//创建连接池对象
DruidDataSource dataSource=new DruidDataSource();
//设置参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/db14");
dataSource.setUsername("root");
dataSource.setPassword("root");复制
方式2:使用工厂类集合配置文件创建连接池,推荐使用
将连接池(druid)抽取到工具类中【重点】🚩
需要做的事: 1 加载properties属性文件 2 创建连接池对象 3 对外提供获取连接池的方法 4 对外提供获取连接的方法 5 对外提供释放资源的方法
自定义连接池
要求:所有的连接池都必须直接或者间接实现DataSource接口,该接口由sun公司制定,相当于连接池的规范,用于规范连接池必须提供哪些方法。
连接池本质:内部会有一个数组或者集合容器,用于存储Connection对象。
为了实现连接对象的归还而不是关闭,引入了以下四种设计模式,其中继承者模式无法实现归还连接对象的需求,另外三种设计模式如下:
装饰者设计模式:
解决的问题:在不改变原有类方法源代码的情况下给方法进行增强,在实现了中调用原有对象的对应方法,也可对原有方法进行增强。
要求:要实现接口的所有方法。
弊端:要重写的方法太多了,写起来麻烦。
理解:创建一个JDBC4connection的兄弟类,除了要进行重写增强的方法外,其他所有的方法同JDBC4connection一样去实现Connection接口里定义的方法
适配器模式:
解决的问题:解决重写的方法太多的问题,如果没有这样的模版了就需要自己写。
要求:需要提前定义一个类(适配器类/模版类)实现接口,重写所有方法,在重写的方法中调用原有对象的对应方法,不做增强。然后我么的类只需要继承该适配器类,重写需要增强的方法即可.
理解:在Connetion接口和要完成增强方法的类之间,创建一个实现类模板,去实现接口中所有的方法,那样在需要在实现增强方法的类里面就不需要实现所有的方法了。
步骤:
【第一步】创建适配器模板类
public abstract class AbstractConnectionAdapter implements Connection {
private Connection conn;//保存原有对象
public AbstractConnectionAdapter(Connection conn) {
this.conn = conn;
}
@Override
public void close() throws SQLException {
conn.close();
}
@Override
public Statement createStatement() throws SQLException {
return conn.createStatement();
}
//其他方法按照上面的写法自己补充。。。
}复制
【第二步】自定义一个Connection类继承适配器类,重写需要增强的方法
public class HeimaConnection extends AbstractConnectionAdapter {
private List<Connection> sPool; //连接池中的容器对象
private Connection conn; //传递进来的conn是JDBC4Connection
public HeimaConnection(Connection conn, List<Connection> sPool) {
super(conn);
this.conn=conn;
this.sPool=sPool;
}
//重新close方法
@Override
public void close() throws SQLException {
//归还连接
sPool.add(conn);
}
}复制
【第三步】在自定义连接池的获取连接方法中,返回自定义的Connection对象
动态代理模式:
相关概念
解决的问题:在不改变原始对象方法源代码的情况下对原始方法进行增强,可以彻底解决装饰模式以及适配器模式要重写的方法过多问题。
要求:代理对象和目标对象要实现相同的接口
三个角色:目标对象、接口、代理对象(在内存中动态生成的对象)
具体做法:使用Proxy.newProxyInstance(...)方法动态生成并返回一个代理对象
动态代理代码演示
【第一步】创建目标对象 --> JDBC4Connection
Connection conn = JDBCUtils.getConnection();
【第二步】使用Proxy.newProxyInstance(三个参数)创建代理对象
ClassLoader loader:和目标对象使用相同类加载,用来动态生成代理对象,一般使用目标对象获取
Class<?>[] interfaces:目标对象实现的接口们,告诉代理对象要和目标对象实现相同的接口
InvocationHandler h:是一个接口,用来处理代理对象要做的事
ClassLoader classLoader = conn.getClass().getClassLoader();
//Class<?>[] interfaces = conn.getClass().getInterfaces(); //在此次无法获取接口们,因为JDBC4Connection没有直接实现Connection接口
Class<?>[] interfaces={Connection.class}; //静态初始化
Connection conn_proxy= (Connection) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
/**
* 调用代理对象的方法,InvocationHandler的invoke方法就执行了
* @param proxy 代理对象
* @param method 调用代理对象的方法对象
* @param args 调用代理对象方法传递的参数们
* @return 该返回值最终会返回到调用代理对象的地方
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke执行了...");
/*System.out.println(method);
System.out.println(Arrays.toString(args));*/
//调用目标对象的方法
//如果是close方法,我们就需要归还连接
if("close".equals(method.getName())){
System.out.println("归还连接...");
return null;
}
//反射调用目标对象的方法
return method.invoke(conn,args); //等价于之前适配器模式中的conn.prepareStatement(sql)之类的操作
}
});
//3 调用代理对象的方法
//conn_proxy.getCatalog();
//String value = conn_proxy.getClientInfo("hello");
//System.out.println(value);
PreparedStatement pstmt = conn_proxy.prepareStatement("select ...", 2);
System.out.println("pstmt = " + pstmt);
conn_proxy.close();
}复制
自定义连接池的getConnection方法中使用动态代理
@Override
public Connection getConnection() throws SQLException {
if(sPool.size()<=0){
throw new SQLException("连接池空空如也!😣");
}
Connection conn = sPool.remove(0); //原始JDBC4Connection,没有归还连接的功能
//创建自定义的连接对象:HeimaConnection_back
//HeimaConnection connection=new HeimaConnection(conn,sPool);//包装一下
//换成使用动态代理得到Connection的代理对象
ClassLoader classLoader = conn.getClass().getClassLoader();
Class<?>[] interfaces={Connection.class}; //静态初始化
//创建代理对象
Connection proxyObj= (Connection) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果是close方法就归还连接
if(method.getName().equals("close")){
sPool.add(conn);
}
//如果不是close方法,就反射调用目的对象的方法
return method.invoke(conn,args);
}
});
//
return proxyObj; //一定是放回代理对象
}复制
DbUtils工具类【扩展】🚩
介绍
DbUtils是apache组织提供的一个封装了jdbc操作的工具类,该工具类提供了简单的CRUD操作,只需要两步就可以轻松操作数据库。
常用API
QueryRunner核心类:负责CRUD操作
BeanListHandler类:用来将查询结果封装成Bean对象,并且将Bean对象存到List集合中返回List<Bean对象>
BeanHandler类:用来将查询结果封装成Bean对象并返回。复制
使用步骤
增删改操作:
【第一步】创建QueryRunner核心对象,需要传递连接池
【第二步】调用update(根据具体情况选参数),返回值为int类型的影响的行数
查询操作:
【第一步】创建QueryRunner核心对象,需要传递连接池
【第二步】调用query(根据具体情况选参数),返回值为查询得到的结果集,结果集的类型视传参的类型而定
事务管理操作(批量添加):
在创建QueryRunner的对象,不传递连接池
手动创建连接对象,开启事务
在批量添加语句中每次都传入手动创建的连接对象
添加成功则提交事务
出现异常则回滚事务复制