• 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() + "	" + rs.getString(1) + "	" + rs.getString(2) + "	" + 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() 方法,也就是将被代理对象从活动队列放到空闲队列,但因为活动队列里存放的都是代理对象,导致无法通过被代理对象从活动队列将代理对象放到空闲队列,进而导致连接资源并没有得到循环利用。

  • 相关阅读:
    常见cout格式输出
    P3332 [ZJOI2013]K大数查询
    pdb
    OS
    ubuntu su failure when password was right
    【opencv安裝】ubuntu16 opencv安装+测试
    shell脚本读取文件+读取命令行参数+读取标准输入+变量赋值+输出到文件
    xshell上传下载文件
    【sed / awk脚本编写】
    shell正则式解析身份证和手机号
  • 原文地址:https://www.cnblogs.com/shamao/p/11937629.html
Copyright © 2020-2023  润新知