Java中用动态代理实现标准的DataSource数据源连接池

首先简单谈谈为什么要用连接池?

大家知道,我们平常连接数据库的时候,首先需要获取到数据库的连接,在Java中对应的是 Connection,建立获取数据库连接是比较消耗资源的,而且每次建立获取连接也比较浪费时间,可以试想,如果每次请求过来,需要访问数据库时,都去重新建立并获取新的连接,会浪费大量的资源和时间,此时客户端的响应时间肯定会较长,这并不是我们想看到的。因此这时候我们就要想办法避免这种现象,所以这时候就可以用连接池来解决。

其实简单的说,连接池实现的主要目的就是,获取连接的时候从池中获取已经初始化好的连接,这样做就不用每次都建立新的连接,就能节省资源的消耗,也能提高效率。然后在用完连接以后,调用conn.close( ); 关闭连接的时候并不是真去把这个连接关闭掉,而是应该把这个连接放回到池中,下次请求过来了,可以继续使用。

Java中连接池具体的实现一般有两种方式,一种是用包装类来实现,另一种是用动态代理来实现

现在常见的开源的Java连接池有DBCP和C3P0等,其中DBCP就是上边说的利用包装类来实现的,而C3P0是动态代理实现的。

今天讲一下动态代理的实现,关于包装类的实现方法,我也简单总结了一下,写了一篇博客

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

有兴趣的可以点击看一下

ok,下面我们简单讲一下动态代理的实现,用动态代理写一个简单的连接池,供新同学参考,高手请自动忽略。

首先贴一下为这次例子我建的一个简单的学生表,数据库是自己电脑虚拟机Linux上装的MySql数据库

超级的简单的一个表,就不多解释了。下面开始进入正题...

第一步:我们需要写一个标准的数据源DataSource,Java中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){
			final Connection conn = pool.remove();
			//返回动态代理对象
			return (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() {
				@Override
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					if("close".equals(method.getName())){
						return pool.add(conn);
					}else {
						return method.invoke(conn, args);
					}
				}
			});
		}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;
	}
	
}

返回动态代理对象需要注意几点:一,代理对象需要和被代理的对象用一个类加载器;二,代理对象需要和被代理的对象实现同样的接口;三,需要new InvocationHandler()来处理对象中具体的方法

第二步:建一个实体类对象,用来接收封装查询出来的数据

public class Student implements Serializable{

	private static final long serialVersionUID = -1672204456370247243L;

	private String id;
	
	private String name;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + "]";
	}
}

这就是个普通的bean对象,很简单,没啥可说的。

第三步:写一个调用端来测试写的连接池

/**
 * 
 * @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();
				}
			}
		}
	
	}

}

可以打断点看一下,返回的 conn 对象已经是$Proxy0,说明返回的已经是代理的对象了,而不是conn自身,而在返回的代理的对象里,我们已经对Close方法做了处理,所以就简单的实现了调用close的时候,不是真正的关闭数据库连接资源,而是把使用完的连接资源还回池中,供别的请求使用或者下次继续使用。

最后运行结果:

好了,一个简单的连接池就实现了,虽然代码比较简陋,但是能讲清楚里边的原理就好啦,哈哈。

大家可以动手试一试,过程中需要导入MySQL的驱动包,不要忘记啦。
 

发布了108 篇原创文章 · 获赞 103 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/ju_362204801/article/details/78886102