zoukankan      html  css  js  c++  java
  • 手写DAO框架(七)-如何保证连接可用

    版权声明:本文为博客园博主「水木桶」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://www.cnblogs.com/shuimutong/p/11408219.html

    背景

    手写DAO框架系列前后更新了5篇文章,外加1篇使用示例,GDAO框架至此已经初具雏形,简单的使用不成问题。接下来就是对框架进行不断的优化。

    顺便说一下性能

    手写DAO框架(六)-后续之框架使用示例 此篇文章对GDAO的性能较少提及,这里就简单的记载一下,后续有机会再更新。

    数据示例:id:14005  name:BatchName-4009  age:52,其中id是自动生成。

    数据库是mariadb,和测试程序位于同一台机器

    测试数据条数5000条,一次调用

    userDao.add(uds[i]);

    方法插入一条数据。

    系统 配置 结果
    macOS 2c4t,8g,固态硬盘 总耗时:8629.0ms,平均耗时:1.726ms
    Win10 6c6t,24g,固态硬盘 总耗时:5153.0ms,平均耗时:1.031ms

     

    一、当前问题分析

    对于一个提供连接池功能的DAO框架,如果保存的连接失效了无法自动移除池,如果连接数据库的网络出现闪断连接无法继续使用,只能通过重启服务来达到初始化连接的目的,这样的做法显然是不够优雅的。

    为了提高框架的稳定性,所以决定对框架的连接部分做一次优化。

    二、需求整理

    通过网上查资料,拟定了几个点。

    1、自动重连

    autoReconnect=true

    JDBC通过配置可实现

    2、连接有效性检测

    a、配置连接检测语句。备注:有的数据库Driver支持ping(),可以使用。

    3、连接泄露检查

    当连接从连接池借出后,长时间(配置时间)不归还,将强制回收。

    4、了解到的其他问题

    如果连接闲置时间过长,可能被mysql主动关闭。

    正常的使用-归还(连接放到队列,队列是先进先出,下次再取,形成一个循环)流程,可避免连接闲置时间过长,暂缓优化。

    优化点确定

    2、连接有效性检测

    3、连接泄露检查

    三、编码实现

    博主开始写代码了,需要一段时间

    连接有效性检测,根据配置来检测,代码参考MysqlValidConnectionChecker。

    连接泄露检查,通过启一条线程,根据配置时间进行检查。

    ----------------------经过了好几天的编写,代码完成了---------------------------------------------

    直接上代码。

    1、增加配置

    #########v03##########
    #连接检测语句
    checkConnectionValidationQuery=select 1
    #归还连接时检测连接,true false
    checkConnectionWhenReturn=true
    #定时检测连接间隔时长(分钟)
    periodCheckConnectionTimeMin=10
    #连接泄露检测
    connectionLeakCheck=true
    #连接泄露检测间隔时长(分钟)
    connectionLeakCheckPeriodTimeMin=10
    #强制归还连接时长(小时)
    forceReturnConnectionTimeHour=6

    说明:

    1)配置可以分为1个基础,3个部分。

    1个基础是指连接检测语句,3个部分分别对应归还连接时检测连接、定时检测连接间隔时长、连接泄露检测

    2)定时检测主要是防止连接中断了不能自动生成新的连接,连接间隔时长如果设为0,则不会定时检测。

    2、主要代码

      1 package me.lovegao.gdao.connection;
      2 
      3 import java.sql.Connection;
      4 import java.sql.SQLException;
      5 import java.util.ArrayList;
      6 import java.util.HashSet;
      7 import java.util.Iterator;
      8 import java.util.List;
      9 import java.util.Map;
     10 import java.util.Map.Entry;
     11 import java.util.Properties;
     12 import java.util.Queue;
     13 import java.util.Set;
     14 import java.util.concurrent.ConcurrentHashMap;
     15 import java.util.concurrent.ConcurrentLinkedQueue;
     16 import java.util.concurrent.ExecutorService;
     17 import java.util.concurrent.Executors;
     18 
     19 import org.apache.commons.lang3.StringUtils;
     20 import org.apache.commons.lang3.math.NumberUtils;
     21 import org.slf4j.Logger;
     22 import org.slf4j.LoggerFactory;
     23 
     24 import me.lovegao.gdao.bean.SystemConstant;
     25 import me.lovegao.gdao.util.ConnectionUtil;
     26 
     27 /**
     28  * 第二版简易连接池实现<br/>
     29  * 主要增加连接有效性检测,包括归还连接时检测,定时检测连接
     30  * 
     31  * @author simple
     32  *
     33  */
     34 public class SimpleV2ConnectionPool extends SimpleConnectionPool {
     35     private final static Logger log = LoggerFactory.getLogger(SimpleV2ConnectionPool.class);
     36     private ExecutorService ES;
     37     // 归还连接时检测连接,这步最好做成异步的,避免影响归还速度
     38     private boolean checkConnectionWhenReturn = false;
     39     // 连接检测语句
     40     private String checkConnectionValidationQuery;
     41     // 定时检测连接的时间(分钟)
     42     private int periodCheckConnectionTimeMin;
     43     /** 待检测连接 **/
     44     private volatile Queue<Connection> TO_CHECK_CONNECTION_POOL;
     45     //查询超时时间
     46     private final int QUERY_TIMEOUT_SECONDS;
     47     //连接泄露检测
     48     private boolean checkConnectionLeak = false;
     49     //连接泄露检测间隔时长-分钟
     50     private int checkConnectionLeakPeriodTimeMin = 30;
     51     //强制归还连接时长(小时)
     52     private double forceReturnConnectionTimeHour;
     53     //连接最大空闲时长(小时)
     54 //    private double connectionMaxIdleTimeHour;
     55     /**连接最后借出时间**/
     56     private Map<Integer, Long> CONNECTION_OUT_TIME_MAP_POOL = null;
     57 
     58     public SimpleV2ConnectionPool(Properties properties) throws Exception {
     59         super(properties);
     60         QUERY_TIMEOUT_SECONDS = super.getQueryTimeoutSecond();
     61         initProp(properties);
     62         initCheck();
     63     }
     64     
     65     private void initProp(Properties properties) {
     66         //连接有效性检测配置
     67         if (properties.containsKey(SystemConstant.STR_CHECK_CONNECTION_VALIDATION_QUERY)) {
     68             checkConnectionValidationQuery = properties
     69                     .getProperty(SystemConstant.STR_CHECK_CONNECTION_VALIDATION_QUERY);
     70             String checkWhenReturn = properties.getProperty(SystemConstant.STR_CHECK_CONNECTION_WHEN_RETURN);
     71             if (checkWhenReturn.toLowerCase().equals("true")) {
     72                 checkConnectionWhenReturn = true;
     73             }
     74             if (properties.containsKey(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN)) {
     75                 String periodTimeStr = properties.getProperty(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN);
     76                 if (StringUtils.isNumeric(periodTimeStr)) {
     77                     periodCheckConnectionTimeMin = Integer.parseInt(periodTimeStr);
     78                 }
     79             }
     80         }
     81         //连接泄露检测配置
     82         if (properties.containsKey(SystemConstant.STR_CONNECTION_LEAK_CHECK)) {
     83             String leakCheckStr = properties.getProperty(SystemConstant.STR_CONNECTION_LEAK_CHECK);
     84             if (leakCheckStr.toLowerCase().equals("true")) {
     85                 String leakCheckPeriodTimeStr = properties.getProperty(SystemConstant.STR_CONNECTION_LEAK_CHECK_PERIOD_TIME_MIN);
     86                 if (StringUtils.isNumeric(leakCheckPeriodTimeStr)) {
     87                     checkConnectionLeakPeriodTimeMin = Integer.parseInt(leakCheckPeriodTimeStr);
     88                 }
     89                 String forceReturnTimeStr = properties.getProperty(SystemConstant.STR_FORCE_RETURN_CONNECTION_TIME_HOUR);
     90                 if (NumberUtils.isNumber(forceReturnTimeStr)) {
     91                     forceReturnConnectionTimeHour = Double.parseDouble(forceReturnTimeStr);
     92                     if(forceReturnConnectionTimeHour > 0) {
     93                         checkConnectionLeak = true;
     94                     }
     95                 }
     96                 //最大空闲检测,功能上和定时检测连接重复,暂时不开发。
     97                 
     98                 //需要同时配置(强制归还时间)才能检测连接泄露
     99                 if(forceReturnConnectionTimeHour > 0) {
    100                     checkConnectionLeak = true;
    101                 }
    102             }
    103         }
    104         StringBuilder infoSb = new StringBuilder();
    105         infoSb.append("SimpleV2ConnectionPoolInitDone------")
    106             .append(",checkConnectionValidationQuery:").append(checkConnectionValidationQuery)
    107             .append(",checkConnectionWhenReturn:").append(checkConnectionWhenReturn)
    108             .append(",periodCheckConnectionTimeMin:").append(periodCheckConnectionTimeMin)
    109             .append(",checkConnectionLeak:").append(checkConnectionLeak)
    110             .append(",checkConnectionLeakPeriodTimeMin:").append(checkConnectionLeakPeriodTimeMin)
    111             .append(",forceReturnConnectionTimeHour:").append(forceReturnConnectionTimeHour);
    112         System.out.println(infoSb.toString());
    113     }
    114 
    115     @Override
    116     public Connection getConnection() throws Exception {
    117         Connection conn = super.getConnection();
    118         if(checkConnectionLeak) {
    119             int connHashCode = conn.hashCode();
    120             CONNECTION_OUT_TIME_MAP_POOL.put(connHashCode, System.currentTimeMillis());
    121         }
    122         return conn;
    123     }
    124 
    125     @Override
    126     public void returnConnection(Connection conn) {
    127         //检测连接泄露
    128         if(checkConnectionLeak) {
    129             int connHashCode = conn.hashCode();
    130             //连接超时过长,已经被主动移除
    131             if(!CONNECTION_OUT_TIME_MAP_POOL.containsKey(connHashCode)) {
    132                 return;
    133             } else {
    134                 CONNECTION_OUT_TIME_MAP_POOL.remove(connHashCode);
    135             }
    136         }
    137         //检测归还连接
    138         if (checkConnectionWhenReturn) {
    139             if(TO_CHECK_CONNECTION_POOL.isEmpty()) {
    140                 synchronized(TO_CHECK_CONNECTION_POOL) {
    141                     if(TO_CHECK_CONNECTION_POOL.isEmpty()) {
    142                         TO_CHECK_CONNECTION_POOL.add(conn);
    143                         TO_CHECK_CONNECTION_POOL.notifyAll();
    144                     } else {
    145                         TO_CHECK_CONNECTION_POOL.add(conn);
    146                     }
    147                 }
    148             } else {
    149                 TO_CHECK_CONNECTION_POOL.add(conn);
    150             }
    151         } else {
    152             superReturnConnection(conn);
    153         }
    154     }
    155     
    156 
    157     @Override
    158     public void closeConnectionPool() {
    159         if(ES != null) {
    160             ES.shutdownNow();
    161         }
    162         super.closeConnectionPool();
    163     }
    164 
    165     private void superReturnConnection(Connection conn) {
    166         super.returnConnection(conn);
    167     }
    168     
    169     private Connection superGetByConnectionHashCode(int hashCode) {
    170         return super.getByConnectionHashCode(hashCode);
    171     }
    172 
    173     // 初始化检查
    174     private void initCheck() {
    175         int threadPoolSize = 0;
    176         if(checkConnectionWhenReturn) {
    177             threadPoolSize += 2;
    178             TO_CHECK_CONNECTION_POOL = new ConcurrentLinkedQueue();
    179         }
    180         if(periodCheckConnectionTimeMin > 0) {
    181             threadPoolSize++;
    182         }
    183         //需要同时配置(强制归还时间、最大空闲时间)才能检测连接泄露
    184         if(checkConnectionLeak) {
    185             threadPoolSize++;
    186             CONNECTION_OUT_TIME_MAP_POOL = new ConcurrentHashMap();
    187         }
    188         if(threadPoolSize > 0) {
    189             ES = Executors.newFixedThreadPool(threadPoolSize);
    190             // 检查归还连接
    191             if(checkConnectionWhenReturn) {
    192                 //启两个线程同时检测
    193                 for(int i=0; i<2; i++) {
    194                     ES.execute(new ReturnConnectionCheck());
    195                 }
    196             }
    197             //定时检测连接
    198             if (periodCheckConnectionTimeMin > 0) {
    199                 ES.execute(new ConnectionPeriodCheck());
    200             }
    201             //连接泄露检测
    202             if(checkConnectionLeak) {
    203                 ES.execute(new ConnectionLeakCheck());
    204             }
    205         }
    206     }
    207 
    208     /**
    209      * 连接泄露定时检测
    210      * @author simple
    211      *
    212      */
    213     class ConnectionLeakCheck implements Runnable {
    214         int sleepTimeMs = checkConnectionLeakPeriodTimeMin * 60 * 1000;
    215         @Override
    216         public void run() {
    217             Set<Integer> preConnHashCodeSet = new HashSet();
    218             while (true) {
    219                 if(ES.isShutdown()) {
    220                     break;
    221                 }
    222                 try {
    223                     Thread.sleep(sleepTimeMs);
    224                 } catch (Exception e) {
    225                     log.error("ConnectionLeakCheckSleepException", e);
    226                 }
    227                 try {
    228                     checkConnectionLeak(preConnHashCodeSet);
    229                 } catch (Exception e) {
    230                     log.error("ConnectionLeakCheckException", e);
    231                 }
    232             }
    233         }
    234     }
    235     
    236     //检测连接泄露
    237     private void checkConnectionLeak(Set<Integer> preConnHashCodeSet) throws Exception {
    238         if(CONNECTION_OUT_TIME_MAP_POOL.size() < 1) {
    239             preConnHashCodeSet = new HashSet();
    240         } else {
    241             Iterator<Entry<Integer, Long>> connHashCodeIt = CONNECTION_OUT_TIME_MAP_POOL.entrySet().iterator();
    242             //先对比前后两次的连接,如果有相同的,再检测相同的连接
    243             if(preConnHashCodeSet.size() == 0) {
    244                 while(connHashCodeIt.hasNext()) {
    245                     preConnHashCodeSet.add(connHashCodeIt.next().getKey());
    246                 }
    247             } else {
    248                 StringBuilder logSb = new StringBuilder();
    249                 long timeFlag = (long) (System.currentTimeMillis() - forceReturnConnectionTimeHour * 3600 * 1000);
    250                 logSb.append("ConnectionLeakCheck---")
    251                     .append(",timeFlag:").append(timeFlag)
    252                     .append(",forceReturnConnectionTimeHour:").append(forceReturnConnectionTimeHour);
    253                 List<Integer> toCloseConnectionHashCodeList = new ArrayList();
    254                 logSb.append(",toCloseConn,{");
    255                 //过滤出两次集合重合,且已经超时的元素
    256                 while(connHashCodeIt.hasNext()) {
    257                     Entry<Integer, Long> connEntry = connHashCodeIt.next();
    258                     int connHashCode = connEntry.getKey();
    259                     if(preConnHashCodeSet.contains(connHashCode) && connEntry.getValue() < timeFlag) {
    260                         toCloseConnectionHashCodeList.add(connHashCode);
    261                         logSb.append(connHashCode).append(":").append(connEntry.getValue()).append(",");
    262                     }
    263                 }
    264                 logSb.append("}");
    265                 if(toCloseConnectionHashCodeList.size() > 0) {
    266                     for(Integer connHashCode : toCloseConnectionHashCodeList) {
    267                         Connection conn = superGetByConnectionHashCode(connHashCode);
    268                         if(conn != null) {
    269                             try {
    270                                 conn.close();
    271                             } catch (SQLException e) {
    272                                 log.error("closeConnectionException", e);
    273                             }
    274                             CONNECTION_OUT_TIME_MAP_POOL.remove(connHashCode);
    275                             superReturnConnection(conn);
    276                         }
    277                     }
    278                 }
    279                 log.info(logSb.toString());
    280                 //进行过一次检测之后,对之前存储的进行初始化
    281                 preConnHashCodeSet = new HashSet();
    282             }
    283         }
    284     }
    285     
    286     /**
    287      * 归还连接检测
    288      * @author simple
    289      *
    290      */
    291     class ReturnConnectionCheck implements Runnable {
    292         @Override
    293         public void run() {
    294             while (true) {
    295                 if(ES.isShutdown()) {
    296                     break;
    297                 }
    298                 Connection toCheckConn = TO_CHECK_CONNECTION_POOL.poll();
    299                 if (toCheckConn == null) {
    300                     try {
    301                         synchronized(TO_CHECK_CONNECTION_POOL) {
    302                             TO_CHECK_CONNECTION_POOL.wait();
    303                         }
    304                     } catch (InterruptedException e) {
    305                         log.error("checkReturnConnectionWaitException", e);
    306                     }
    307                 } else {
    308                     boolean canUse = ConnectionUtil.isValidConnection(toCheckConn, checkConnectionValidationQuery,
    309                             QUERY_TIMEOUT_SECONDS);
    310                     if (!canUse) {
    311                         try {
    312                             toCheckConn.close();
    313                         } catch (SQLException e) {
    314                             log.error("checkReturnConnectionCloseConnException", e);
    315                         }
    316                     }
    317                     superReturnConnection(toCheckConn);
    318                 }
    319             }
    320         }
    321     }
    322     
    323     /**
    324      * 连接定时检测
    325      * @author simple
    326      *
    327      */
    328     class ConnectionPeriodCheck implements Runnable {
    329         int sleepTimeMs = periodCheckConnectionTimeMin * 60 * 1000;
    330         @Override
    331         public void run() {
    332             while (true) {
    333                 if(ES.isShutdown()) {
    334                     break;
    335                 }
    336                 try {
    337                     Thread.sleep(sleepTimeMs);
    338                 } catch (Exception e) {
    339                     log.error("checkReturnConnectionSleepException", e);
    340                 }
    341                 while(true) {
    342                     //是否继续检测
    343                     boolean continueCheck = false;
    344                     Connection toCheckConn = null;
    345                     try {
    346                         toCheckConn = getConnection();
    347                         if (toCheckConn != null) {
    348                             boolean canUse = ConnectionUtil.isValidConnection(toCheckConn,
    349                                     checkConnectionValidationQuery, QUERY_TIMEOUT_SECONDS);
    350                             if (!canUse) {
    351                                 toCheckConn.close();
    352                                 //连接不可用,继续检测其他连接是否正常
    353                                 continueCheck = true;
    354                                 log.info("oneConnectionCannotUse,closeIt.....");
    355                             }
    356                         }
    357                     } catch (Exception e) {
    358                         log.error("checkReturnConnectionException", e);
    359                         continueCheck = true;
    360                     } finally {
    361                         if (toCheckConn != null) {
    362                             superReturnConnection(toCheckConn);
    363                         }
    364                     }
    365                     if(continueCheck) {
    366                         log.info("checkOneConnectionCannotUse,beginToCheckOtherConnection....");
    367                         try {
    368                             Thread.sleep(500);
    369                         } catch (InterruptedException e) {
    370                             log.error("checkReturnConnectionWaitException", e);
    371                         }
    372                     } else {
    373                         break;
    374                     }
    375                 }
    376             }
    377         }
    378     }
    379 }

    3、主要思路

    1)归还连接时检测连接的思路

    归还连接的时候,如果不采用异步,那么归还连接的线程必须等待连接确认完毕之后才能继续执行,这样做感觉性能不是最优的。

    所以引入了异步,归还连接时,连接直接放到一个待检测的容器里,不需要等待检测完之后再返回。

    待检测连接由检测线程异步进行检测。检测现场从待检测容器里取连接进行检测,必然会出现空的情况。

    出现了空的情况怎么做好呢,是在那里自旋等待?是休眠一段时间再检测?还是等待呢?

    自旋等待,浪费计算资源;休眠的话,休眠时长不好确定,谁知道下一毫秒会发生什么?万一因为连接未及时检测出现了连接用尽,岂不是很尴尬?

    所以,我选择了等待,当线程归还时,主动唤醒等待线程。

    代码实现之后,测试的时候,我发现运行报错。wait()、notifyAll()方法不是那样用的,需要获取锁。

    添加了锁之后,为了尽量减少同步带来的性能损失,我采取了写单例时经常提到的双重检查:我不需要每次都要拿锁、通知,只需要在待检测连接池是空的时候才需要进行拿锁、通知。、

    2)连接泄露检测的思路

    连接泄露检测,主要是为了防止连接被借出去之后,很久都没有归还的情景。

    很久不归还,这个连接还占着连接池的坑,却没法被复用,所以需要进行检测。

    检测需要确定的就是,取出多久算久?然后就是,多久检测一次?

    具体实现的时候,还有一个问题,就是强制归还的连接应该怎么归还?直接放回连接池吗?万一连接真的还在被别的线程使用怎么办?

    所以这里,我采取先把连接关闭了,然后再归还。

    3)定时检测连接的思路

    为了保持连接池的连接都是最终可用的,所以需要对连接池的连接进行定时的检测。

    如果连接不可用,就把连接关闭,然后从连接池去除。

    4、测试代码

    1)配置

    ##驱动名称
    driverName=com.mysql.jdbc.Driver
    ##连接url
    connectionUrl=jdbc:mysql://localhost:3306/simple?autoReconnect=true&useServerPrepStmts=false&rewriteBatchedStatements=true&connectTimeout=1000&useUnicode=true&characterEncoding=utf-8
    ##用户名
    userName=simple
    ##用户密码
    userPassword=123456
    ##初始化连接数
    initConnectionNum=10
    ##最大连接数
    maxConnectionNum=50
    ##最大查询等待时间
    maxQueryTime=3
    #########v03##########
    #归还连接时检测连接,true false
    checkConnectionWhenReturn=true
    #连接检测语句
    checkConnectionValidationQuery=select 1
    #定时检测连接间隔时长(分钟)
    periodCheckConnectionTimeMin=1
    #连接泄露检测
    connectionLeakCheck=false
    #连接泄露检测间隔时长(分钟)
    connectionLeakCheckPeriodTimeMin=1
    #强制归还连接时长(小时)
    forceReturnConnectionTimeHour=0.01

    2)测试代码

    package me.lovegao.gdao.connpool;
    
    import java.sql.Connection;
    import java.util.Properties;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import me.lovegao.gdao.bean.SystemConstant;
    import me.lovegao.gdao.connection.IConnectionPool;
    import me.lovegao.gdao.connection.SimpleV2ConnectionPool;
    
    public class V2ConnectionPoolTest {
        private final static Logger log = LoggerFactory.getLogger(V2ConnectionPoolTest.class);
    
        public static void main(String[] args) throws Exception {
            String dbPath = "mysql2.properties";
            log.info("hello-----------------");
            log.warn("hello-----------------");
            periodCheckConnection(dbPath);
        }
        
        /**
         * 连接泄露检测
         * @param dbPath
         * @throws Exception
         */
        public static void checkConnectionLeak(String dbPath) throws Exception {
            Properties dbProp = CommonUtil.loadProp(dbPath);
            dbProp.setProperty(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN, "10");
            dbProp.setProperty(SystemConstant.STR_CONNECTION_LEAK_CHECK, "true");
            IConnectionPool connPool = new SimpleV2ConnectionPool(dbProp);
            Connection conn = connPool.getConnection();
            Thread.sleep(240000);
            connPool.returnConnection(conn);
            Thread.sleep(61000);
            connPool.closeConnectionPool();
        }
        
        /**
         * 连接定时检测
         * @param dbPath
         * @throws Exception
         */
        public static void periodCheckConnection(String dbPath) throws Exception {
            Properties dbProp = CommonUtil.loadProp(dbPath);
            dbProp.setProperty(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN, "1");
            IConnectionPool connPool = new SimpleV2ConnectionPool(dbProp);
            Thread.sleep(120000);
            Connection conn = connPool.getConnection();
            connPool.returnConnection(conn);
            Thread.sleep(2061000);
    //        connPool.closeConnectionPool();
        }
        
        /**
         * 归还连接检测
         * @param dbPath
         * @throws Exception
         */
        public static void checkWhenReturn(String dbPath) throws Exception {
            Properties dbProp = CommonUtil.loadProp(dbPath);
            IConnectionPool connPool = new SimpleV2ConnectionPool(dbProp);
            Connection conn = connPool.getConnection();
            conn.close();
            connPool.returnConnection(conn);
            Thread.sleep(20000);
            connPool.closeConnectionPool();
        }
    
    }

    我是分布执行的测试,通过debug来校验的流程,面对这种项目,不知道单测该如何写。

    我是通过中间把数据库关了,又打开,来连接定时检测的。结果证明没有问题。

    框架优化版本已提交到git:https://github.com/shuimutong/gdao.git,欢迎指点

    相关测试代码:https://github.com/shuimutong/useDemo.git  ./gdao-demo下。 

    -----------------------本文完------------------------------

  • 相关阅读:
    Globus Toolkit 4.0正式版发布
    五四爱国纪念歌——最早反映五四运动的歌曲(zz)
    历史上的昨天和今天(zz)
    苹果:2.7GHz G5比3.6GHz P4快98%(zz)
    上海地铁又有人跳轨……
    使用IPAddress解析IP地址时要注意的问题
    WinHEC:电脑硬件和微软OS发展历程展(zz)
    emplace_back与push_back方法的区别
    处理Excel,填充空白区域
    折腾词库,一个词库互转程序
  • 原文地址:https://www.cnblogs.com/shuimutong/p/11408219.html
Copyright © 2011-2022 走看看