前言
在数据库开发中,连接池是一种重要的技术手段,它可以提高数据库连接的复用性和性能。连接池的原理是在应用启动时创建一定数量的数据库连接,并将这些连接保存在一个池中,应用程序需要数据库连接时,从连接池中获取一个连接进行操作,不再频繁地创建和关闭连接。
为了更好地利用连接池并实现连接的复用,我们可以借助JDK动态代理机制来实现连接对象的自动重用。通过在动态代理中拦截连接的获取和归还操作,我们可以对连接对象进行有效地管理,确保每次使用完毕后将连接归还到连接池中,从而实现连接的复用,提高性能和效率。
接下来,我们将结合JDK动态代理机制和连接池技术,实现一个基于动态代理的连接池复用功能。
如果不了解 jdk 动态代理的,可以去回顾一下我的这篇文章: http://t.csdnimg.cn/BJnzh
一、开始学习
1、新建项目,结构如下
2、导入依赖
<!-- spring 的核心依赖 -->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
3、在 pool 包下新建三个类,ConnectionInvocationHandler、ConnectionPool、ConnUtils
ConnUtils 类
@Slf4j
public class ConnUtils {
public static Connection getConnection(String url, String name, String password) {
try {
return DriverManager.getConnection(url, name, password);
} catch (SQLException e) {
throw new RuntimeException("connection erro", e);
}
}
}
主要是用来获取数据库连接的工具类
ConnectionPool (连接池)
@Setter
public class ConnectionPool {
/**
* 连接池(存放连接的集合)
*/
private LinkedList<Connection> pool = new LinkedList<>();
/**
* 连接属性
*/
private String url;
private String name;
private String password;
/**
* 池大小
*/
private Integer poolSize;
/**
* 在构造方法中初始化连接池大小
*
*/
public void init() throws SQLException {
for (int i = 0; i < poolSize; i++) {
// 1、从数据库获取连接对象
Connection connection = ConnUtils.getConnection(url,name,password);
// 2、对连接对象创建代理
connection = createProxy(connection);
// 3、将连接对象返回连接池中
connection.close();
}
}
/**
* 为连接对象创建代理
*
* @param connection
* @return
*/
private Connection createProxy(Connection connection) {
// 创建回调处理器
ConnectionInvocationHandler connectionInvocationHandler = new ConnectionInvocationHandler(connection,pool);
// 获取连接对象的所有接口
Class<?>[] interfaces = new Class[]{Connection.class};
// 获取类加载器
ClassLoader loader = ConnectionPool.class.getClassLoader();
// 创建代理
connection = (Connection) Proxy.newProxyInstance(loader,interfaces,connectionInvocationHandler);
return connection;
}
/**
* 从池里获取代理连接
*
* @return
*/
public Connection getConnection() {
return pool.removeFirst();
}
/**
* 查看连接池的大小
* @return
*/
public int size(){
return pool.size();
}
}
这个类是一个连接池的实现,用于管理数据库连接对象。连接池的作用是在应用程序初始化时创建一定数量的数据库连接,并将这些连接保存在连接池中,当应用程序需要使用数据库连接时,可以从连接池中获取连接,使用完毕后将连接放回连接池,以便其他线程或请求继续使用。
连接池可以提高数据库的性能和效率,主要有以下几个方面的作用:
连接的复用:连接池中已经创建好的连接可以被反复利用,避免频繁地创建和关闭连接,从而减少了连接的创建和销毁开销。
连接的管理:连接池可以对连接进行有效的管理,包括连接的创建、销毁、状态的监控等,确保连接的可用性和稳定性。
连接的限制:连接池可以设置最大连接数,防止因为连接过多导致数据库负载过高或内存资源耗尽,从而提高系统的稳定性和安全性。
通过使用连接池,可以减少数据库连接的创建和销毁次数,提高数据库访问的效率,同时能够更好地管理和控制数据库连接的使用,确保系统的性能和可靠性。
ConnectionInvocationHandler类
public class ConnectionInvocationHandler implements InvocationHandler {
/**
* 目标对象(被代理对象)
*/
private Connection connection;
/**
* 连接池
*/
private LinkedList<Connection> pool;
public ConnectionInvocationHandler(Connection connection, LinkedList<Connection> pool) {
this.connection = connection;
this.pool = pool;
}
public ConnectionInvocationHandler(Connection connection) {
this.connection = connection;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果当前调用的是 Connection 的 close 方法则将它放回到池中
if ("close".equals(method.getName())) {
// 从池的尾部放回去
// 注意:这里放回池中的必须是代理对象,而不是目标 Connection
pool.addLast((Connection) proxy);
return null;
} else {
// 除 close 以外的其他方法则正常调用目标对象的行为
return method.invoke(connection, args);
}
}
}
这是一个代理类,实现了
InvocationHandler
接口,用于代理连接对象。主要作用是拦截连接对象的close
方法,并将其放回连接池中,以便其他线程或请求继续使用。在代理对象调用
close
方法时,ConnectionInvocationHandler
会将代理对象(即当前正在被使用的连接)放回到连接池中,而不是直接关闭代理对象。这样可以确保连接池中的连接资源得到充分利用,提高应用程序的性能。在代理对象调用除
close
以外的方法时,ConnectionInvocationHandler
会将方法调用转发给目标对象(即被代理的连接对象),并返回方法的结果。这样可以保证应用程序的正常逻辑,对于使用者来说,无需关心连接对象被代理的具体实现,只需要按照正常的方式使用连接对象即可。总之,
ConnectionInvocationHandler
是整个连接池的核心之一,它负责管理连接对象的生命周期,确保连接对象能够得到充分利用和正确释放,保障应用程序的性能和可靠性。
4、在 resources 包下新建一个 db.properties 文件
url = jdbc:mysql://localhost:3306/psm
name = root
password = 123456
poolSize = 5
5、在 config 包下新建一个 AppConfig 类
@Configuration
@PropertySource("classpath:db.properties")
@Slf4j
public class AppConfig {
@Value("${url}")
private String url;
@Value("${name}")
private String userName;
@Value("${password}")
private String password;
@Value("${poolSize}")
private Integer poolSize;
/**
* 装配连接池
*
* @return
*/
@Bean
public ConnectionPool connectionPool() throws SQLException {
log.info("url:" + url);
log.info("name:" + userName);
log.info("password:" + password);
log.info("size:" + poolSize);
// 创建连接池并设置相关的属性
ConnectionPool pool = new ConnectionPool();
pool.setUrl(url);
pool.setName(userName);
pool.setPassword(password);
pool.setPoolSize(poolSize);
// 初始化连接池
pool.init();
return pool;
}
}
这是一个用于配置连接池的类。通过
@Configuration
注解将它标记为一个配置类,并使用@PropertySource
注解指定了属性文件的位置。在该类中,使用了
@Value
注解来注入属性值。根据属性文件中的配置,注入了url
、name
、password
、poolSize
等属性值,分别表示数据库的URL、用户名、密码和连接池的大小。在
connectionPool()
方法中,创建了一个ConnectionPool
对象,并通过setter方法设置了相关属性的值。然后调用init()
方法初始化连接池,使其预先创建一定数量的数据库连接。最后将创建好的连接池对象返回,以供其他组件或类进行使用。
整个配置类的作用是将属性文件中的配置值注入到连接池对象中,并初始化连接池,以方便应用程序使用数据库连接。
6、测试
@Slf4j
public class Main {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 从容器中获取连接池
ConnectionPool connectionPool = context.getBean(ConnectionPool.class);
log.info("连接数:" + connectionPool.size());
Connection conn1 = connectionPool.getConnection();
Connection conn2 = connectionPool.getConnection();
log.info("连接数:" + connectionPool.size());
conn1.close();
log.info("连接数:" + connectionPool.size());
}
}
运行结果
二、 这个案例是干嘛的
这个案例主要用于配置和初始化连接池,以便在应用程序中高效地管理数据库连接。它具有以下几个作用:
抽象化数据库连接:通过使用连接池,应用程序可以从简单的直接获取和释放数据库连接的方式转变为通过连接池来获取和释放连接。连接池负责维护一定数量的数据库连接,并在需要时提供连接给应用程序使用。这样可以避免频繁地创建和关闭连接,提高数据库操作的效率。
提高性能和资源利用率:连接池可以预先创建一定数量的数据库连接,并将其保存在池中。当应用程序需要数据库连接时,可以从连接池中获取连接,而不是每次都创建一个新的连接。这样可以避免了频繁地创建和销毁连接的开销,提高了数据库操作的性能和资源的利用率。
简化配置和管理:通过将连接池的配置信息(如URL、用户名、密码、连接池大小等)放入属性文件中,并通过配置类进行加载和注入,可以方便地进行配置和管理。这使得连接池的配置可以与应用程序的其他部分分离,易于维护和修改。
总之,这个案例提供了一种在应用程序中配置和初始化连接池的方式,帮助管理数据库连接,提高数据库操作的性能和资源利用率,同时简化了配置和管理的过程。
三、总结
这只是一个利用 jdk 动态代理实现的一个简单的连接池的案例,仅仅知识实现了连接池的复用,连接池中还有很多的 API 方法。
四、gitee 案例
地址:ch17/src/main/java/edu/nf/ch17/pool · qiuqiu/spring-framework - 码云 - 开源中国 (gitee.com)