文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

在项目中使用C3P0作为数据库连接池,被技术总监怼了

2024-12-03 05:13

关注

本文转载自微信公众号「Java极客技术」,作者鸭血粉丝。转载本文请联系Java极客技术公众号。

 一、介绍

数据库连接是一项非常关键的、有限的、昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。

记得之前做的一个项目,当时的应用程序配置的c3p0数据库连接池,最大允许的连接数是500,结果上线没多久,并发量直接上来了,导致大量的数据插入失败,当晚的心情可想而知~

从那一次事故之后,让我对应用程序的数据库连接数有了一次深刻的认识,为了防止再次栽跟头,特意抽了一个时间来编写程序测试案例,用于测试各个数据源连接池的稳定性,以防止自己再次踩坑!

话不多说,直接撸起来!

二、程序实例

熟悉 web 系统开发的同学,基本都知道,在 Java 生态中开源的常用数据库连接池有以下几种:

今天我们就一起来对比一下,这三种数据源连接池的稳定性。

2.1、创建测试表

下面以 mysql 数据库为例,首先创建一个t_test表,方面后续进行插入数据操作。

  1. CREATE TABLE t_test ( 
  2.   id bigint(20) unsigned NOT NULL COMMENT '主键ID'
  3.   name varchar(32) NOT NULL COMMENT '名称'
  4.   PRIMARY KEY (id) 
  5. ) ENGINE=InnoDB COMMENT='测试表'

2.2、 编写测试用例

以dbcp为例,首先创建一个dbcp-jdbc.properties配置文件。

  1. username=root 
  2. password=Hello@123456 
  3. driverClassName=com.mysql.jdbc.Driver 
  4. url=jdbc:mysql://192.168.31.200:3306/testdb?useUnicode=true&characterEncoding=UTF-8 
  5. initialSize=5 
  6. maxActive=1000 
  7. maxIdle=5 
  8. removeAbandoned=ture 
  9. removeAbandonedTimeout=20 
  10. logAbandoned=true 
  11. maxWait=100 

接着,创建一个连接池工具DbcpJdbcUtil。

  1. public class DbcpJdbcUtil { 
  2.   
  3.  private static final Logger logger = LoggerFactory.getLogger(DbcpJdbcUtil.class); 
  4.   
  5.   
  6.  private static Properties prop = new Properties();  
  7.   
  8.  private static BasicDataSource dataSource = null
  9.  // 它是事务专用连接! 
  10.  private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); 
  11.   
  12.  static { 
  13.   classPathSourceRead(); 
  14.  } 
  15.  
  16.     private static void classPathSourceRead(){ 
  17.      //读取指定位置的配置文档(读取class目录文件) 
  18.      try { 
  19.       logger.info("jdbc路径:" + SysConstants.getValue()); 
  20.    prop.load(DbcpJdbcUtil.class.getClassLoader().getResourceAsStream(SysConstants.getValue())); 
  21.    logger.info("数据配置信息" + JSON.toJSONString(prop)); 
  22.    logger.info("初始化默认jdbc配置文件成功!"); 
  23.   } catch (Exception e) { 
  24.    logger.error("初始化默认jdbc文件失败!",e); 
  25.   } 
  26.     } 
  27.      
  28.   
  29.  public static BasicDataSource getDataSource() throws Exception { 
  30.   try { 
  31.    if (dataSource == null) { 
  32.     synchronized (DbcpJdbcUtil.class) { 
  33.      if (dataSource == null) { 
  34.       dataSource = new BasicDataSource(); 
  35.       dataSource.setUsername(prop.getProperty("username")); 
  36.       dataSource.setPassword(prop.getProperty("password")); 
  37.       dataSource.setDriverClassName(prop.getProperty("driverClassName")); 
  38.       dataSource.setUrl(prop.getProperty("url")); 
  39.       dataSource.setInitialSize(Integer.valueOf(prop.getProperty("initialSize"))); 
  40.       dataSource.setMaxActive(Integer.valueOf(prop.getProperty("maxActive"))); 
  41.       dataSource.setMaxIdle(Integer.valueOf(prop.getProperty("maxIdle"))); 
  42.       dataSource.setRemoveAbandoned(Boolean.valueOf(prop.getProperty("removeAbandoned"))); 
  43.       dataSource.setRemoveAbandonedTimeout(Integer.valueOf(prop.getProperty("removeAbandonedTimeout"))); 
  44.       dataSource.setLogAbandoned(Boolean.valueOf(prop.getProperty("logAbandoned"))); 
  45.       dataSource.setMaxWait(Integer.valueOf(prop.getProperty("maxWait"))); 
  46.      } 
  47.     } 
  48.    } 
  49.    return dataSource; 
  50.   } catch (Exception e) { 
  51.    logger.error("根据数据库名称获取数据库资源失败," , e); 
  52.    throw new Exception("根据数据库名称获取数据库资源失败"); 
  53.   } 
  54.  } 
  55.   
  56.   
  57.  public static Connection getConnection() throws Exception { 
  58.   try { 
  59.    Connection con = tl.get(); 
  60.    // 当con不等于null,说明已经调用过beginTransaction(),表示开启了事务! 
  61.    if (con != null
  62.     return con; 
  63.    return getDataSource().getConnection(); 
  64.   } catch (Exception e) { 
  65.    logger.error("获取数据库连接失败!", e); 
  66.    throw new SQLException("获取数据库连接失败!"); 
  67.   } 
  68.  } 
  69.   
  70.   
  71.  public static void beginTransaction() throws Exception { 
  72.   try { 
  73.    Connection con = tl.get(); 
  74.    if (con != null) { 
  75.     con.close(); 
  76.     tl.remove(); 
  77.     //throw new SQLException("已经开启了事务,就不要重复开启了!"); 
  78.    } 
  79.    con = getConnection(); 
  80.    con.setAutoCommit(false); 
  81.    tl.set(con); 
  82.   } catch (Exception e) { 
  83.    logger.error("数据库事物开启失败!", e); 
  84.    throw new SQLException("数据库事物开启失败!"); 
  85.   } 
  86.  } 
  87.  
  88.   
  89.  public static void commitTransaction() throws SQLException { 
  90.   Connection con = tl.get(); 
  91.   try { 
  92.    if (con == null
  93.     throw new SQLException("还没有开启事务,不能提交!"); 
  94.    con.commit(); 
  95.   } catch (Exception e) { 
  96.    logger.error("数据库事物提交失败!", e); 
  97.    throw new SQLException("数据库事物提交失败!"); 
  98.   } finally { 
  99.    if (con != null) { 
  100.     con.close(); 
  101.    } 
  102.    tl.remove(); 
  103.   } 
  104.  } 
  105.   
  106.   
  107.  public static void rollbackTransaction() throws SQLException { 
  108.   Connection con = tl.get(); 
  109.   try { 
  110.    if (con == null
  111.     throw new SQLException("还没有开启事务,不能回滚!"); 
  112.    con.rollback(); 
  113.   } catch (Exception e) { 
  114.    logger.error("数据库事物回滚失败!", e); 
  115.    throw new SQLException("数据库事物回滚失败!"); 
  116.   } finally { 
  117.    if (con != null) { 
  118.     con.close(); 
  119.    } 
  120.    tl.remove(); 
  121.   } 
  122.  } 
  123.   
  124.   
  125.  public static void releaseConnection(Connection connection) throws SQLException { 
  126.   try { 
  127.    Connection con = tl.get(); 
  128.    // 判断它是不是事务专用,如果是,就不关闭! 如果不是事务专用,那么就要关闭! 
  129.    // 如果con == null,说明现在没有事务,那么connection一定不是事务专用的! 
  130.    //如果con != null,说明有事务,那么需要判断参数连接是否与con相等,若不等,说明参数连接不是事务专用连接 
  131.    if (con == null || con != connection
  132.     connection.close(); 
  133.   } catch (Exception e) { 
  134.    logger.error("数据库连接释放失败!", e); 
  135.    throw new SQLException("数据库连接释放失败!"); 
  136.   } 
  137.  } 
  138.  

最后,编写单元测试程序DBCPTest。

  1. public class DBCPTest { 
  2.   
  3.  private static final int sumCount = 1000000; 
  4.   
  5.  private static final int threadNum = 600; 
  6.   
  7.  private void before(String path) { 
  8.   SysConstants.putValue(path); 
  9.   new DBCPService().insert("delete from t_test"); 
  10.  } 
  11.   
  12.  @Test 
  13.  public void testMysql() { 
  14.   long start = System.currentTimeMillis(); 
  15.   String path = "config/mysql/dbcp-jdbc.properties"
  16.   before(path); 
  17.   for (int i =0; i < 1; i++) { 
  18.    String sql = "insert into t_test(id,name) values('" +i+ "','dbcp-mysql-" + i + "')"
  19.    new DBCPService().insert(sql); 
  20.   } 
  21.   System.out.println("耗时:" + (System.currentTimeMillis() - start)); 
  22.  } 
  23.   
  24.  @Test 
  25.  public void testThreadMysql() throws InterruptedException { 
  26.   String path = "config/mysql/dbcp-jdbc.properties"
  27.   before(path); 
  28.   BlockingQueue queue = new LinkedBlockingQueue(); 
  29.   for (int i = 0; i < sumCount; i++) { 
  30.    String sql = "insert into t_test(id,name) values('" +i+ "','dbcp-mysql-" + i + "')"
  31.    queue.put(sql); 
  32.   } 
  33.   long start = System.currentTimeMillis(); 
  34.   final CountDownLatch countDownLatch = new CountDownLatch(threadNum); 
  35.   for (int i = 0; i < threadNum; i++) { 
  36.    final int finalI = i + 1; 
  37.    new Thread(new Runnable() { 
  38.     @Override 
  39.     public void run() { 
  40.      System.out.println("thread " + finalI + " start"); 
  41.      boolean isGo = true
  42.      while (isGo) { 
  43.       String sql = queue.poll(); 
  44.       if(sql != null) { 
  45.        new DBCPService().insert(sql); 
  46.       }else { 
  47.        isGo =false
  48.        System.out.println("thread " + finalI + " finish"); 
  49.        countDownLatch.countDown(); 
  50.       } 
  51.      } 
  52.     } 
  53.    }).start(); 
  54.   } 
  55.   countDownLatch.await();  
  56.   System.out.println("耗时:" + (System.currentTimeMillis() - start)); 
  57.  } 
  58.  

c3p0、druid的配置也类似,这里就不在重复介绍了!

三、性能测试

程序编写完成之后,下面我们就一起来结合各种不同的场景来测试一下各个数据连接池的表现。

为了进一步扩大测试范围,本次测试还将各个主流的数据库也拉入进去,测试的数据库分别是:mysql-5.7、oracle-12、postgresql-9.6

3.1、插入10万条数据

首先,我们来测试一下,各个数据库插入10万条数据,采用不同的数据源连接池,看看它们的表现如何?

测试druid执行结果

从上面测试结果,我们可以基本得出如下结论:

其中druid对postgresql的支持性能最好,c3p0的表现比较差!

3.2、插入100万条数据

可能有的同学,还不太认可,下面我们就来测试一下插入100万条,看看它们的表现如何?

从上面测试结果,我们可以基本得出如下结论:

还是一样的结论,druid对postgresql的支持性能最好,c3p0的表现比较差!

四、小结

从上面的测试结果,我们可以很清晰的看到,在数据连接池方面,druid和dbcp旗鼓相当,而并发方面druid的稳定性大于dbcp,c3p0相比druid和dbcp,稳定性和执行速度要弱些。

在数据库方面,postgresql速度要优于oracle,而oracle对各个数据源的支持和稳定性要有优势,mysql相比oracle和postgresql,执行速度要弱些。

如果在实际开发中,数据源连接池推荐采用druid,数据库的选用方面 postgresql > oracle > mysql。

 

来源:Java极客技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯