设计模式与连接池:享元模式共享连接、工厂模式创建连接、代理模式管理连接
连接池这个东西,做后端开发的兄弟应该都不陌生。说白了,它就是一组预先创建好的数据库连接,放在一个池子里等着用。为什么要这么干?因为每次请求都去创建连接、销毁连接,开销太大了。我早年在一个电商项目里就吃过这个亏——并发一上来,数据库连接直接被打满,系统瞬间瘫痪。
那连接池跟设计模式有什么关系?关系大了去了。你仔细想想,连接池的核心逻辑,其实就是三个设计模式的组合拳:享元模式负责共享连接,工厂模式负责创建连接,代理模式负责管理连接。今天我就带你把这套组合拳拆开揉碎了讲清楚。
1. 享元模式:连接复用,避免重复创建
享元模式的核心思想就一句话:共享细粒度对象,减少内存占用。连接池里的连接,本质上就是享元对象。每个连接都是重量级的,创建一次不容易,能复用就复用。
我习惯把连接池里的连接分为两种状态:空闲态和忙碌态。空闲的连接放在一个队列里,谁要用就拿走,用完再还回来。这就是典型的享元模式——内部状态(连接本身)不变,外部状态(当前谁在用)动态变化。
来看一段简化的代码,感受一下:
// 享元对象:数据库连接
public class DatabaseConnection {
private String url;
private String username;
private String password;
private boolean inUse; // 外部状态:是否被占用
public DatabaseConnection(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
this.inUse = false;
}
public void connect() {
// 实际建立连接
System.out.println("连接到数据库: " + url);
}
public void setInUse(boolean inUse) {
this.inUse = inUse;
}
public boolean isInUse() {
return inUse;
}
}
嗯,这里要注意:享元对象本身不负责管理自己的状态。谁在用、用完了没有,这些信息是由连接池来维护的。这就是后面代理模式要干的事。
2. 工厂模式:统一创建,隐藏细节
连接不能凭空产生,得有个地方负责创建。工厂模式就是干这个的。我个人习惯把连接工厂设计成抽象工厂,这样不同的数据库(MySQL、PostgreSQL、Oracle)都可以通过同一个接口来创建连接。
你想想看,如果每个地方都直接 new Connection(),那换数据库的时候得改多少代码?用工厂模式,只需要改工厂的实现类就行了。我在一个老项目里就见过这种惨状——代码里到处都是 new OracleConnection(),后来要换成 MySQL,改了一个通宵。
// 抽象工厂
public interface ConnectionFactory {
DatabaseConnection createConnection();
}
// MySQL 工厂
public class MySQLConnectionFactory implements ConnectionFactory {
@Override
public DatabaseConnection createConnection() {
return new DatabaseConnection("jdbc:mysql://localhost:3306/mydb", "root", "password");
}
}
// PostgreSQL 工厂
public class PostgreSQLConnectionFactory implements ConnectionFactory {
@Override
public DatabaseConnection createConnection() {
return new DatabaseConnection("jdbc:postgresql://localhost:5432/mydb", "postgres", "password");
}
}
3. 代理模式:管理连接,控制访问
连接从池子里拿出来了,用完了得还回去。但问题是,调用方可能直接调用 connection.close(),把连接给关了。这就麻烦了——连接一旦关闭,就不能再复用了。
代理模式就是来解决这个问题的。我给每个连接包一层代理,把 close() 方法拦截掉。调用方以为自己在关闭连接,实际上代理只是把连接标记为「空闲」,然后放回池子里。
我曾经在一个金融项目里遇到过这种坑:开发人员忘记归还连接,导致连接池被耗尽。后来我加了一层代理,在代理里做了超时检测——如果某个连接被占用超过 30 秒,自动回收并报警。效果立竿见影。
// 代理类:接管连接的生命周期
public class ConnectionProxy {
private DatabaseConnection realConnection;
private ConnectionPool pool;
public ConnectionProxy(DatabaseConnection realConnection, ConnectionPool pool) {
this.realConnection = realConnection;
this.pool = pool;
}
// 代理 close 方法
public void close() {
// 不真正关闭,而是归还到池中
realConnection.setInUse(false);
pool.releaseConnection(realConnection);
System.out.println("连接已归还到池中");
}
// 其他方法直接委托给真实连接
public void executeQuery(String sql) {
if (!realConnection.isInUse()) {
throw new RuntimeException("连接已被释放");
}
System.out.println("执行查询: " + sql);
}
}
4. 三种模式的协作流程
这三种模式不是各自为战,而是紧密协作的。我画了一张图,帮你理清它们之间的关系:
流程其实很简单:客户端请求连接 -> 代理模式拦截请求 -> 从享元模式的池子里拿空闲连接 -> 如果没有空闲,工厂模式创建新连接 -> 代理把连接包装后返回给客户端 -> 客户端用完调用 close() -> 代理拦截 close(),把连接归还到池中。
5. 完整代码示例
最后,我把三个模式整合到一起,给你一个完整的连接池实现。这个代码我在多个项目里都用过,核心逻辑基本不变。
// 连接池:整合三种设计模式
public class ConnectionPool {
private List<DatabaseConnection> idleConnections = new ArrayList<>();
private List<DatabaseConnection> busyConnections = new ArrayList<>();
private ConnectionFactory factory;
private int maxSize = 10;
public ConnectionPool(ConnectionFactory factory) {
this.factory = factory;
// 初始化时创建 3 个连接
for (int i = 0; i < 3; i++) {
DatabaseConnection conn = factory.createConnection();
conn.connect();
idleConnections.add(conn);
}
}
// 获取连接(代理模式)
public ConnectionProxy getConnection() {
DatabaseConnection conn = null;
if (!idleConnections.isEmpty()) {
conn = idleConnections.remove(0);
} else if (busyConnections.size() < maxSize) {
// 享元模式:没有空闲就创建新的
conn = factory.createConnection();
conn.connect();
} else {
throw new RuntimeException("连接池已满,无法获取连接");
}
conn.setInUse(true);
busyConnections.add(conn);
return new ConnectionProxy(conn, this);
}
// 归还连接(享元模式)
public void releaseConnection(DatabaseConnection conn) {
busyConnections.remove(conn);
idleConnections.add(conn);
}
}
好了,三种设计模式在连接池中的应用,我就讲到这里。说白了,享元模式管复用,工厂模式管创建,代理模式管生命周期。这三者配合好了,连接池就能稳定、高效地运行。你在实际项目中遇到连接池相关的问题,不妨从这三个角度去分析,往往能找到症结所在。