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

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

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

部分内容来自以下博客:

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

一个简单的数据库连接池

连接池工具类

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

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

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

代码如下:

  1 public class DataPoolUtils {
  2     // 活动连接,使用线程安全的队列
  3     private static LinkedBlockingQueue<Connection> busy = new LinkedBlockingQueue<Connection>();
  4     // 空闲连接,使用线程安全的队列
  5     private static LinkedBlockingQueue<Connection> idle = new LinkedBlockingQueue<Connection>();
  6     // 已创建连接数,使用原子操作类实现线程安全
  7     private static AtomicInteger createCount = new AtomicInteger(0);
  8     // 最大连接数
  9     private static int maxConnection = 5;
 10     // 最大等待毫秒数
 11     private static int maxWaitTimeout = 1000;
 12     
 13     /**
 14      * 创建连接
 15      * @return
 16      * @throws Exception 
 17      */
 18     private Connection createConnection() throws Exception {
 19         Properties pros = new Properties();
 20         InputStream is = DataPoolUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
 21         pros.load(is);
 22         String driverClass = pros.getProperty("driverClass");
 23         Class.forName(driverClass);
 24         String url = pros.getProperty("url");
 25         String user = pros.getProperty("user");
 26         String password = pros.getProperty("password");
 27         return DriverManager.getConnection(url, user, password);
 28     }
 29     
 30     /**
 31      * 关闭连接
 32      * @param connection
 33      */
 34     private void closeConnection(Connection connection) {
 35         try {
 36             if (!connection.isClosed()) {
 37                 connection.close();
 38             }
 39         } catch (SQLException e) {
 40             e.printStackTrace();
 41         }
 42     }
 43     
 44     /**
 45      * 获取连接
 46      * @return
 47      * @throws Exception 
 48      */
 49     public Connection getConnection() throws Exception {
 50         // 尝试获取空闲连接
 51         Connection connection = idle.poll();
 52         if (connection == null) {
 53             // 尝试创建连接,使用双重CAS检查现有连接数是否小于最大连接数
 54             if (createCount.get() < maxConnection) {
 55                 if (createCount.incrementAndGet() <= maxConnection) {
 56                     connection = createConnection();
 57                 } else {
 58                     createCount.decrementAndGet();
 59                 }
 60             }
 61             // 尝试等待获取空闲连接,实现超时等待机制
 62             if (connection == null) {
 63                 connection = idle.poll(maxWaitTimeout, TimeUnit.MILLISECONDS);
 64                 if (connection == null) {
 65                     throw new Exception("获取连接超时");
 66                 }
 67             }
 68         }
 69         busy.offer(connection);
 70         return connection;
 71     }
 72     
 73     /**
 74      * 归还连接
 75      * @param connection
 76      */
 77     public void releaseConnection(Connection connection) {
 78         // 处理空连接
 79         if (connection == null) {
 80             createCount.decrementAndGet();
 81             return;
 82         }
 83         // 处理移除失败的连接
 84         boolean removeResult = busy.remove(connection);
 85         if (!removeResult) {
 86             closeConnection(connection);
 87             createCount.decrementAndGet();
 88             return;
 89         }
 90         // 处理已经关闭的连接
 91         try {
 92             if (connection.isClosed()) {
 93                 createCount.decrementAndGet();
 94                 return;
 95             }
 96         } catch (SQLException e) {
 97             e.printStackTrace();
 98         }
 99         // 处理添加失败的连接
100         boolean offerResult = idle.offer(connection);
101         if (!offerResult) {
102             closeConnection(connection);
103             createCount.decrementAndGet();
104             return;
105         }
106     }
107 }

测试连接池的业务类

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

代码如下:

 1 public class TestPool {
 2     // 根据配置文件里的名称创建连接池
 3     private static DataPoolUtils pool = new DataPoolUtils();
 4 
 5     /**
 6      * 主程序
 7      */
 8     public static void main(String[] args) {
 9         // 模拟多次对数据库的查询操作
10         for (int i = 0; i < 6; i++) {
11             new Thread(new Runnable() {
12                 @Override
13                 public void run() {
14                     select();
15                 }
16             }, "线程" + i).start();
17         }
18     }
19 
20     /**
21      * 查询程序
22      */
23     public static void select() {
24         Connection conn = null;
25         PreparedStatement pstmt = null;
26         ResultSet rs = null;
27         // 获取连接并执行SQL
28         try {
29             conn = pool.getConnection();
30             pstmt = conn.prepareStatement("select * from student where id = 906");
31             rs = pstmt.executeQuery();
32             while (rs.next()) {
33                 System.out.println(Thread.currentThread().getName() + "\t" + rs.getString(1) + "\t" + rs.getString(2) + "\t" + rs.getString("address"));
34             }
35         } catch (Exception e) {
36             e.printStackTrace();
37         } finally {
38             // 释放资源
39             try {
40                 rs.close();
41             } catch (SQLException e) {
42                 e.printStackTrace();
43             }
44             try {
45                 pstmt.close();
46             } catch (SQLException e) {
47                 e.printStackTrace();
48             }
49             /*
50             try {
51                 conn.close();
52             } catch (SQLException e) {
53                 e.printStackTrace();
54             }
55             */
56             pool.releaseConnection(conn);
57         }
58     }
59 }

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

改进说明

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

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

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

 1 /**
 2  * 代理处理类
 3  */
 4 class ConnectionInvocationHandler implements InvocationHandler{
 5     private Connection connection;
 6     private DataPoolUtils dpu;
 7 
 8     public ConnectionInvocationHandler(DataPoolUtils dpu, Connection connection) {
 9         this.dpu = dpu;
10         this.connection = connection;
11     }
12 
13     @Override
14     public Object invoke(Object proxy, Method method, Object[] args)
15             throws Throwable {
16         // 对原生的关闭方法进行修改
17         if(method.getName().equals("close")){
18             dpu.releaseConnection(connection);
19             return null;
20         }else{
21             return method.invoke(connection, args);
22         }
23     }
24 }

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

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

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

1 try {
2     conn.close();
3 } catch (SQLException e) {
4     e.printStackTrace();
5 }

注意说明

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

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

    原文作者:鲨猫
    原文地址: https://www.cnblogs.com/shamao/p/11937629.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞