您的位置:首页 > 编程语言 > Java开发

Java中用包装模式实现标准的DataSource数据源连接池

2017-12-26 00:26 357 查看


本篇续上篇“Java中用动态代理实现标准的DataSource数据源连接池”之后,继续谈谈利用包装设计模式如何实现一个简单的数据源连接池。

上篇已经大概讲过了为什么我们需要连接池,而且上篇也说了一下,Java中实现连接池的做法主要有2种,本篇就详细的讲述一下用包装设计模式实现一个连接池。

首先大家来考虑一个问题,在上篇也提过说,连接池的核心功能就是在用完conn资源后,需要关闭释放资源,但是我们并不想真正的把这个连接资源关闭,而应该放回池中,供其他请求使用,或者供下次继续使用。说到这儿,肯定很多人会想到,如果是这样的话,那肯定就得在conn对象的close方法上做点文章啊,那就改一下conn中的close方法啊,在close方法中撸点代码实现刚才说的不去真正关闭连接资源,而是放回池中,这样不就好了吗?

这么想貌似是没什么问题哈,但是仔细想想就会发现一个大问题,那就是,Connection是JDBC规范中的一个接口啊。

具体的怎么获取到数据库的连接(也就是上边一直说的conn对象)那是具体不同的数据库厂商来实现的,也就是咱们平常项目中导入的数据库驱动jar包中,oracle驱动中有oracle的实现方式,mysql驱动中有mysql的实现方式。这些数据库厂商们他们都实现了JDBC的接口规范,也就是说数据库厂商肯定实现了Connection这个接口(因为这是JDBC的规范),具体到底怎么获取到数据库连接,他们在实现类里肯定有实现。就等于说你用的是oracle数据库那这个conn对象就是oracle驱动jar包给你返回的;你用的是mysql数据库那这个conn对象就是mysql的驱动jar包给你返回的。

所以说这个conn对象是数据库驱动包里的,如果是像上边说的直接去改一下conn中的close方法来实现把连接放回池中,那不就是去改数据库驱动的jar包的源码吗?这简直是天方夜谭啊兄弟,肯定行不通的!

那写到到这儿估计有的小伙伴会有一个疑问,那数据库厂商们直接把这个close给写好不就好了,也免的咱们去改了,多麻烦,而且关键是咱也改不了啊。。。

问题在于数据库厂商是实现的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接口里所有的方法,里边有几十个方法,代码太长了,贴上来影响阅读,我就省略号了。。。大家自己写的时候需要全部实现。

第二步:

/**
*
* @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对象。

最后的运行结果:



到这儿用包装设计模式来实现连接池就搞定啦,代码还是比较简单,大家可以动手试一试。

供大家参考,若有错误的地方希望大家包涵并及时指出,3Q~
古耐~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息