如何设计数据库连接池
曾经在面试的时候,就被问到如何设计连接池。当时就没有回答上来,就与offer擦肩而过。今天就来动手写一个简易版数据库连接池。
为什么需要连接池?数据库连接是一种比较宝贵资源,其创建或销毁都需要系统的开销。如果频繁创建、销毁,那势必会造成系统性能低下。为了提高资源的复用性,我们可以把创建的连接放入池中,需要的时候从池中获取,不需要的时候归还到池。这样我们就大大节省系统资源开销,还提高系统的性能。
难点
设计数据库连接池的难点在于:如何去归还到池中。
所用技术
动态代理:动态代理也可以理解为拦截器,可以在调用方法之前,去做一些事情。
JDK的代理基于接口,需要实现 InvocationHandler
接口
这里给出一个简单的例子。
public class Test {
public static void main(String[] args) {
A proxy = (A)Proxy.newProxyInstance(A.class.getClassLoader(), new Class[]{
A.class}, new B());
proxy.close(); // 打印方法名
}
interface A {
void close();
}
static class B implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return null;
}
}
}
动态代理其实是JVM在运行期动态帮我们创建接口 A
的实例。动态创建的代理类大概是这个样子的。
public class AProxy implements A {
InvocationHandler handler;
public AProxy(InvocationHandler handler) {
this.handler = handler;
}
@Override
public void close() {
handler.invoke(this, A.class.getMethod("close"), new Object[]{
});
}
}
数据库连接池
在mybatis
中就实现数据库连接池,这里根据mybatis
源码去掉一些统计功能,写一个简易的数据库连接池,便于我们能够理解其中设计原理。
数据库连接池有几个参数:最大活跃数、最大空闲数、最大空闲时间。
PoolState
PoolState
类是用来存放活跃的连接和空闲连接。
public class PoolState {
private final List<PoolConnection> activeConnections = new ArrayList<>();
private final List<PoolConnection> idleConnections = new ArrayList<>();
public List<PoolConnection> getActiveConnections() {
return activeConnections;
}
public List<PoolConnection> getIdleConnections() {
return idleConnections;
}
}
PoolConnection
PoolConnection
类对 Connection
进行一层包装。它实现 InvocationHandler
主要用来在调用 close
方法时,并不是真正去关闭数据库,而是把连接放回到连接池中(poolDataSource)。PoolConnection
类有两个Connection
对象,realConnection
是数据库连接实例,proxyConnection
是JVM动态地创建Connection
的对象。客户端拿到的是proxyConnection
对象。
public class PoolConnection implements InvocationHandler {
private final static String CLOSE = "close";
private static final Class<?>[] IFACES = new Class<?>[] {
Connection.class };
private final PoolDataSource dataSource;
private final Connection realConnection;
private final Connection proxyConnection;
private long keepAliveTime;
public PoolConnection(Connection conn, PoolDataSource dataSource