大白话聊懂Java中的连接池,用包装模式实现标准的DataSource数据源连接池
一、首先简单谈谈为什么要用连接池?
大家知道,我们平常连接数据库的时候,首先需要获取到数据库的连接,在Java中对应的是 Connection,建立获取数据库连接是比较消耗资源的,而且每次建立获取连接也比较浪费时间,可以试想,如果每次请求过来,需要访问数据库时,都去重新建立并获取新的连接,会浪费大量的资源和时间,此时客户端的响应时间肯定会较长,这并不是我们想看到的。因此这时候我们就要想办法避免这种现象,所以这时候就可以用连接池来解决。
其实简单的说,连接池实现的主要目的就是,获取连接的时候从池中获取已经初始化好的连接,这样做就不用每次都建立新的连接,就能节省资源的消耗,也能提高效率。
然后在用完连接以后,调用conn.close( ); 关闭连接的时候并不是真去把这个连接关闭掉,而是应该把这个连接放回到池中,下次请求过来了,可以继续使用。
Java中连接池具体的实现一般有两种方式,一种是用包装类来实现,另一种是用动态代理来实现
现在常见的开源的Java连接池有DBCP和C3P0等,其中DBCP就是上边说的利用包装类来实现的,而C3P0是动态代理实现的。
今天讲一下使用包装类实现一个简单的连接池
刚才也过说过了,连接池的核心功能就是在用完conn资源后,需要关闭释放资源,但是我们并不想真正的把这个连接资源关闭,而应该放回池中,供其他请求使用,或者供下次继续使用。
说到这儿,肯定很多人会想到,如果是这样的话,那肯定就得在conn对象的close方法上做点文章啊,那就改一下conn中的close方法啊,在close方法中撸点代码实现刚才说的不去真正关闭连接资源,而是放回池中,这样不就好了吗?
这么想貌似是没什么问题,但是你细品就会发现一个大问题,那就是:Connection是JDBC规范中的一个接口啊。具体的怎么获取到数据库的连接(也就是上边一直说的conn对象)那是具体不同的数据库厂商来实现的,也就是咱们平常项目中导入的数据库驱动jar包中,oracle驱动jar包中有oracle的实现方式,mysql驱动jar包中有mysql的实现方式。这些数据库厂商们他们都实现了JDBC的接口规范,也就是说数据库厂商肯定实现了Connection这个接口(因为这是JDBC的规范),具体到底怎么获取到数据库连接,他们在实现类里肯定有实现。就等于说如果你用的是oracle数据库,那么这个conn对象就是oracle驱动jar包给你返回的;如果你用的是mysql数据库,那么这个conn对象就是mysql的驱动jar包给你返回的。
所以说这个conn对象是数据库驱动包里的,如果是像上边说的直接去改一下conn中的close方法来实现把连接放回池中,那岂不是去修改数据库驱动jar包中的源码吗?这简直是天方夜谭啊兄dei,肯定行不通的!
那写到到这儿估计有小伙伴估计会有另外一个疑问:那数据库厂商们直接把这个close方法给写成咱们想要的不就好了,也免的咱们去改了,多麻烦(而且关键是jar包里的内容咱也改不了啊。。。)
咱们继续思考:各个数据库厂商实现的是JDBC规范,而JDBC规范中的close方法就是要求立即关闭连接,并不是把连接放回池中。
所以数据库厂商的实现类中这么实现close方法并没有问题,数据库厂商负责实现的close就应该是立即释放资源。
下面的截图是JDK API中官方对Connection接口中close方法的解释
所以数据库厂商本身就不应该关心连接池的问题,如果它close方法的做法是把连接放回池中,那它放回到哪个池中呢,它是不是还要写个连接池什么的?这肯定不科学啊,对吧。
关于怎么更好、更高效率的去使用这些连接,这不是数据库厂商要考虑的事情,而是连接池需要考虑的,所以就出现了DBCP、C3P0这些连接池。
好了,问题及分析就先说到这儿。
接下来就该考虑:既然上边说的方法行不通,那该怎么办呢?
第一种比较能想到的方式就是利用Java中的包装设计模式来解决
第二种就是用动态代理返回conn的代理对象来解决
今天就来讲一下第一种用包装来实现。
撸码之前先简单的讲一下利用包装实现的原理:
其实很简单,咱们实现的目标不就是在调用close方法的时候做一下处理吗,其他的方法该调原有对象还调原有对象的方法。
所以咱们就自定义一个自己的类也实现Connection接口,然后把原有的conn对象通过构造方法注入进来,为什么要注入原有的conn对象呢?因为调用其他方法的时候咱们必须得用原有的conn对象来调用啊,所以原有的对象必须得注入进来。最后在使用用conn对象的地方,把咱们自定义的conn对象返回去就好了。
二、具体代码实现
第一步:写一个自定义的conn类
/**
*
* @author caoju
*
*/
public class MyConnection implements Connection{
//原有的数据库连接
private Connection conn;
//数据源中的池
private LinkedList<Connection> pool;
//通过构造函数注入
public MyConnection(Connection conn,LinkedList<Connection> pool){
this.conn = conn;
this.pool = pool;
}
@Override
public void close() throws SQLException {
//close方法做咱们需要的处理,也就是放回池中
pool.add(conn);
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
//其他的方法什么改变都不做,还是用原来的conn对象来调用
//所以除了close方法咱们特殊处理一下,其他的方法没一点变化,保持原有的功能
return conn.prepareStatement(sql);
}
@Override
public Statement createStatement() throws SQLException {
//其他的方法什么改变都不做,还是用原来的conn对象来调用
//所以除了close方法咱们特殊处理一下,其他的方法没一点变化,保持原有的功能
return conn.createStatement();
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
//下边的方法一样都用原有的对象把调用保持下去
//由于Connection接口中方法很多,在这儿就不一一搞了
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
// TODO Auto-generated method stub
return null;
}
.......
}
这里需要注意一个问题就是,由于自定义的MyConnection类也实现了Connection接口,所以需要实现Connection接口里所有的方法,里边有几十个方法,代码太长了,贴上来影响阅读,我就省略号了。。。大家自己写的时候需要全部实现。
第二步:自定义一个DataSource
/**
*
* @author caoju
*
*/
public class MyDataSource implements DataSource{
private static LinkedList<Connection> pool = new LinkedList<Connection>();
private static final String name = "com.mysql.jdbc.Driver";
private static final String url = "jdbc:mysql://192.168.199.188:3306/cj";
private static final String user = "root";
private static final String password = "123456";
static{//利用静态代码块儿在类一加载的时候就初始化10个连接到池中
try {
Class.forName(name);
for(int i=0;i<10;i++){
Connection conn = DriverManager.getConnection(url, user, password);
pool.add(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Connection getConnection() throws SQLException {
if(pool.size()>0){
Connection conn = pool.remove();
//此处返回的已经不是原有的conn对象了,而是咱们自定义的包装类对象
return new MyConnection(conn,pool);
}else{
throw new RuntimeException("对不起,服务器忙...");
}
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO Auto-generated method stub
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public Connection getConnection(String username, String password)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
}
第三步:写个调用端测试一下
/**
*
* @author caoju
*
*/
public class Client {
public static void main(String[] args) {
DataSource ds = new MyDataSource();
Connection conn = null;
PreparedStatement ps = null;
try {
conn = ds.getConnection();
ps = conn.prepareStatement("select * from student");
ResultSet rs = ps.executeQuery();
List<Student> stuList = new ArrayList<Student>();
while(rs.next()){
Student student = new Student();
String id = rs.getString("id");
String name = rs.getString("name");
student.setId(id);
student.setName(name);
stuList.add(student);
}
for (Student student : stuList) {
System.out.println(student);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
最后,打个断点瞅一眼
可以看到,返回的对象是咱们自定义的MyConnection类,而不是原有的conn对象。
最后的运行结果:
到这儿用包装设计模式来实现连接池就搞定啦,代码还是比较简单,大家可以动手试一试。
供大家参考,若有错误的地方希望大家包涵并及时指出
铁子们,如果觉得文章对你有所帮助,可以点关注,点赞