Java操作数据库——手动实现数据库连接池

摘要:本文主要学习了如何手动实现一个数据库连接池,以及在这基础上的一些改进。

部分内容来自以下博客:

https://blog.csdn.net/soonfly/article/details/72731144

一个简单的数据库连接池

连接池工具类

连接池使用了线程安全的队列存储连接资源,保证了线程安全。

提供了获取连接和释放连接的方法,实现了连接资源的循环使用。

在对线程进行技术时,使用原子类,保证了线程计数在多线程环境下的安全。

代码如下:

 public class DataPoolUtils {
// 活动连接,使用线程安全的队列
private static LinkedBlockingQueue<Connection> busy = new LinkedBlockingQueue<Connection>();
// 空闲连接,使用线程安全的队列
private static LinkedBlockingQueue<Connection> idle = new LinkedBlockingQueue<Connection>();
// 已创建连接数,使用原子操作类实现线程安全
private static AtomicInteger createCount = new AtomicInteger(0);
// 最大连接数
private static int maxConnection = 5;
// 最大等待毫秒数
private static int maxWaitTimeout = 1000; /**
* 创建连接
* @return
* @throws Exception
*/
private Connection createConnection() throws Exception {
Properties pros = new Properties();
InputStream is = DataPoolUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
pros.load(is);
String driverClass = pros.getProperty("driverClass");
Class.forName(driverClass);
String url = pros.getProperty("url");
String user = pros.getProperty("user");
String password = pros.getProperty("password");
return DriverManager.getConnection(url, user, password);
} /**
* 关闭连接
* @param connection
*/
private void closeConnection(Connection connection) {
try {
if (!connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
} /**
* 获取连接
* @return
* @throws Exception
*/
public Connection getConnection() throws Exception {
// 尝试获取空闲连接
Connection connection = idle.poll();
if (connection == null) {
// 尝试创建连接,使用双重CAS检查现有连接数是否小于最大连接数
if (createCount.get() < maxConnection) {
if (createCount.incrementAndGet() <= maxConnection) {
connection = createConnection();
} else {
createCount.decrementAndGet();
}
}
// 尝试等待获取空闲连接,实现超时等待机制
if (connection == null) {
connection = idle.poll(maxWaitTimeout, TimeUnit.MILLISECONDS);
if (connection == null) {
throw new Exception("获取连接超时");
}
}
}
busy.offer(connection);
return connection;
} /**
* 归还连接
* @param connection
*/
public void releaseConnection(Connection connection) {
// 处理空连接
if (connection == null) {
createCount.decrementAndGet();
return;
}
// 处理移除失败的连接
boolean removeResult = busy.remove(connection);
if (!removeResult) {
closeConnection(connection);
createCount.decrementAndGet();
return;
}
// 处理已经关闭的连接
try {
if (connection.isClosed()) {
createCount.decrementAndGet();
return;
}
} catch (SQLException e) {
e.printStackTrace();
}
// 处理添加失败的连接
boolean offerResult = idle.offer(connection);
if (!offerResult) {
closeConnection(connection);
createCount.decrementAndGet();
return;
}
}
}

测试连接池的业务类

为了能够实现线程的循环使用,需要调用线程池的释放连接资源的方法,而不是将连接资源直接关闭。

代码如下:

 public class TestPool {
// 根据配置文件里的名称创建连接池
private static DataPoolUtils pool = new DataPoolUtils(); /**
* 主程序
*/
public static void main(String[] args) {
// 模拟多次对数据库的查询操作
for (int i = 0; i < 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
select();
}
}, "线程" + i).start();
}
} /**
* 查询程序
*/
public static void select() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
// 获取连接并执行SQL
try {
conn = pool.getConnection();
pstmt = conn.prepareStatement("select * from student where id = 906");
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(Thread.currentThread().getName() + "\t" + rs.getString(1) + "\t" + rs.getString(2) + "\t" + rs.getString("address"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
/*
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
*/
pool.releaseConnection(conn);
}
}
}

使用动态代理修改原生连接的关闭方法

改进说明

简单的数据库连接池已经有了,但是在使用的时候如果调用了原生的关闭方法,会导致连接不能重复使用。

利用之前学过的动态代理进行改进,使调用关闭方法的时候执行的仍然是连接池里的释放资源的方法。

在 DataPoolUtils 工具类里添加动态代理的相关内部类:

 /**
* 代理处理类
*/
class ConnectionInvocationHandler implements InvocationHandler{
private Connection connection;
private DataPoolUtils dpu; public ConnectionInvocationHandler(DataPoolUtils dpu, Connection connection) {
this.dpu = dpu;
this.connection = connection;
} @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 对原生的关闭方法进行修改
if(method.getName().equals("close")){
dpu.releaseConnection(connection);
return null;
}else{
return method.invoke(connection, args);
}
}
}

修改 DataPoolUtils 工具类中 public Connection getConnection() 方法的返回值,将返回值改为使用动态代理后的值:

 return (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(),
new Class[] { Connection.class },
new ConnectionInvocationHandler(this, connection));

修改 TestPool 业务类中的 public static void select() 方法,将释放连接改为关闭连接:

 try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}

注意说明

在工具类的 getConnection() 方法中返回代理类,而不是在工具类的 createConnection() 方法中返回,是因为通过后者得到的对象是要放到活动队列里的,如果在后者中返回代理对象,那么就会导致活动队列里的对象都是代理对象。

那么在执行代理对象的 close() 方法时,经过动态代理后,实际上是执行的是被代理对象的 releaseConnection() 方法,也就是将被代理对象从活动队列放到空闲队列,但因为活动队列里存放的都是代理对象,导致无法通过被代理对象从活动队列将代理对象放到空闲队列,进而导致连接资源并没有得到循环利用。

Java操作数据库——手动实现数据库连接池的更多相关文章

  1. Java操作数据库——使用连接池连接数据库

    Java操作数据库——使用连接池连接数据库 摘要:本文主要学习了如何使用JDBC连接池连接数据库. 传统方式和连接池方式 传统方式的步骤 使用传统方式在Java中使用JDBC连接数据库,完成一次数据库 ...

  2. JDBC 数据库连接 Java操作数据库 jdbc快速入门

    JDBC基本概念 Java DataBase Connectivity 数据库连接 java操作数据库 本质上(sun公司的程序员)定义的一套操作关系型数据库的规则 既接口  更新内容之前 代码 pa ...

  3. 第77节:Java中的事务和数据库连接池和DBUtiles

    第77节:Java中的事务和数据库连接池和DBUtiles 前言 看哭你,字数:8803,承蒙关照,谢谢朋友点赞! 事务 Transaction事务,什么是事务,事务是包含一组操作,这组操作里面包含许 ...

  4. JDBC数据源(DataSource)数据源技术是Java操作数据库的一个很关键技术,流行的持久化框架都离不开数据源的应用。

    JDBC数据源(DataSource)的简单实现   数据源技术是Java操作数据库的一个很关键技术,流行的持久化框架都离不开数据源的应用. 2.数据源提供了一种简单获取数据库连接的方式,并能在内部通 ...

  5. Java Web(九) JDBC及数据库连接池及DBCP,c3p0,dbutils的使用

    DBCP.C3P0.DBUtils的jar包和配置文件(百度云盘):点我下载 JDBC JDBC(Java 数据库连接,Java Database Connectify)是标准的Java访问数据库的A ...

  6. Java操作数据库——使用JDBC连接数据库

    Java操作数据库——使用JDBC连接数据库 摘要:本文主要学习了如何使用JDBC连接数据库. 背景 数据持久化 数据持久化就是把数据保存到可掉电式存储设备中以供之后使用.大多数情况下,特别是企业级应 ...

  7. java操作数据库:增删改查

    不多bb了直接上. 工具:myeclipse 2016,mysql 5.7 目的:java操作数据库增删改查商品信息 test数据库的goods表 gid主键,自增 1.实体类Goods:封装数据库数 ...

  8. Java操作数据库——在JDBC里使用事务

    Java操作数据库——在JDBC里使用事务 摘要:本文主要学习了如何在JDBC里使用事务. 使用Connection的事务控制方法 当JDBC程序向数据库获得一个Connection对象时,默认情况下 ...

  9. JDBC 4.0 开始Java操作数据库不用再使用 Class.forName加载驱动类了

    JDBC 4.0 开始Java操作数据库不用再使用 Class.forName加载驱动类了 代码示例 转自 https://docs.oracle.com/javase/tutorial/jdbc/o ...

随机推荐

  1. .NET Core依赖注入集成Dynamic Proxy

    在<Castle DynamicProxy基本用法>中介绍了如何将DP与Autofac集成使用,而 .NET Core有自己的依赖注入容器,在不依赖第三方容器的基础上,如何实现动态代理就成 ...

  2. spring boot2 修改默认json解析器Jackson为fastjson

    0.前言 fastjson是阿里出的,尽管近年fasjson爆出过几次严重漏洞,但是平心而论,fastjson的性能的确很有优势,尤其是大数据量时的性能优势,所以fastjson依然是我们的首选:sp ...

  3. Litho在美团动态化方案MTFlexbox中的实践

    1. MTFlexbox MTFlexbox是美团内部应用的非常成熟的一种跨平台动态化解决方案,它遵循了CSS3中提出的Flexbox规范来抹平多平台的差异.MTFlexbox适用于重展示.轻交互的业 ...

  4. STT-MRMA技术优点

    到目前为止,设计人员可以使用的存储技术是易变的,这意味着在断电后,存储器中的数据内容会丢失.但是,随着Everspin Technologies推出256Mb STT-MRAM,系统现在可以拥有像DR ...

  5. crontab 定时任务没有响应 检测步骤

    设置规则 # 每分钟执行一次 */1 * * * * /scripts/script.sh # 每小时执行一次 0 */1 * * * /scripts/script.sh # 每天 02:00 执行 ...

  6. 好用的Markdown编辑器安利-Typora

    Typora,一款还用极简优秀的免费开源Markdown编辑器,非常值得每一位爱好Markdown的朋友学习和使用.我个人是深深被它吸引了,不论是写博客还是记笔记,Typora都是我十足的好帮手.Ty ...

  7. Newifi-mini OpenWrt 下 EAP-PEAP,EAP-TLS 企业级无线认证及 FreeRadius3

    Newifi-mini OpenWrt 下 EAP-PEAP,EAP-TLS 企业级无线认证及 FreeRadius3 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-07-15. ...

  8. C语言笔记 05_判断&循环&函数

    判断 C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 false. 下面是大多数编程语言中典型的判断结构的一般形式: 判断语句 C 语言提供了以下类型的判断语句.点击链接查看每 ...

  9. Java生鲜电商平台-生鲜电商中商品类目、属性、品牌、单位架构设计与实战

    Java生鲜电商平台-生鲜电商中商品类目.属性.品牌.单位架构设计与实战 说明:Java生鲜电商平台-生鲜电商中商品类目.属性.品牌.单位架构设计与实战经验分享 凡是涉及到购物,必然是建立在商品的基础 ...

  10. Prism_Event Aggregator(4)

    Event Aggregator Prism库提供了一种事件机制,可以在应用程序中松散耦合的组件之间进行通信.该机制基于事件聚合器服务,允许发布者和订阅者通过事件进行通信,但仍然没有彼此直接引用. 在 ...