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

 

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

 

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

首先大家来考虑一个问题,在上篇也提过说,连接池的核心功能就是在用完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接口里所有的方法,里边有几十个方法,代码太长了,贴上来影响阅读,我就省略号了。。。大家自己写的时候需要全部实现。

第二步:

/**
 * 
 * @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~ 古耐~

铁子们,如果觉得文章对你有所帮助,可以点关注,点赞,也可以加wx:ju_luguoxingfu,也可以扫码,欢迎一起学习交流

3Q~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曹举的个人博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值