这篇文章主要讲解了“数据库连接池的方式有哪几种”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“数据库连接池的方式有哪几种”吧!

一、介绍

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

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

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

话不多说,直接撸起来!

二、程序实例

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

dbcp:DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池,DBCP可以直接的在应用程序中使用,Tomcat的数据源使用的就是DBCP

c3p0:c3p0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection和Statement池的DataSources对象

druid:阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个ProxyDriver,一系列内置的JDBC组件库,一个SQL Parser。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。

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

2.1、创建测试表

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

CREATETABLEt_test(idbigint(20)unsignedNOTNULLCOMMENT'主键ID',namevarchar(32)NOTNULLCOMMENT'名称',PRIMARYKEY(id))ENGINE=InnoDBCOMMENT='测试表';

2.2、 编写测试用例

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

username=rootpassword=Hello@123456driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://192.168.31.200:3306/testdb?useUnicode=true&characterEncoding=UTF-8initialSize=5maxActive=1000maxIdle=5removeAbandoned=tureremoveAbandonedTimeout=20logAbandoned=truemaxWait=100

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

publicclassDbcpJdbcUtil{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(DbcpJdbcUtil.class);/**jdbc配置文件*/privatestaticPropertiesprop=newProperties();privatestaticBasicDataSourcedataSource=null;//它是事务专用连接!privatestaticThreadLocal<Connection>tl=newThreadLocal<Connection>();static{classPathSourceRead();}privatestaticvoidclassPathSourceRead(){//读取指定位置的配置文档(读取class目录文件)try{logger.info("jdbc路径:"+SysConstants.getValue());prop.load(DbcpJdbcUtil.class.getClassLoader().getResourceAsStream(SysConstants.getValue()));logger.info("数据配置信息"+JSON.toJSONString(prop));logger.info("初始化默认jdbc配置文件成功!");}catch(Exceptione){logger.error("初始化默认jdbc文件失败!",e);}}/***从连接池获取数据源*@return*@throwsException*/publicstaticBasicDataSourcegetDataSource()throwsException{try{if(dataSource==null){synchronized(DbcpJdbcUtil.class){if(dataSource==null){dataSource=newBasicDataSource();dataSource.setUsername(prop.getProperty("username"));dataSource.setPassword(prop.getProperty("password"));dataSource.setDriverClassName(prop.getProperty("driverClassName"));dataSource.setUrl(prop.getProperty("url"));dataSource.setInitialSize(Integer.valueOf(prop.getProperty("initialSize")));dataSource.setMaxActive(Integer.valueOf(prop.getProperty("maxActive")));dataSource.setMaxIdle(Integer.valueOf(prop.getProperty("maxIdle")));dataSource.setRemoveAbandoned(Boolean.valueOf(prop.getProperty("removeAbandoned")));dataSource.setRemoveAbandonedTimeout(Integer.valueOf(prop.getProperty("removeAbandonedTimeout")));dataSource.setLogAbandoned(Boolean.valueOf(prop.getProperty("logAbandoned")));dataSource.setMaxWait(Integer.valueOf(prop.getProperty("maxWait")));}}}returndataSource;}catch(Exceptione){logger.error("根据数据库名称获取数据库资源失败,",e);thrownewException("根据数据库名称获取数据库资源失败");}}/***使用连接池返回一个连接对象**@return*@throwsSQLException*/publicstaticConnectiongetConnection()throwsException{try{Connectioncon=tl.get();//当con不等于null,说明已经调用过beginTransaction(),表示开启了事务!if(con!=null)returncon;returngetDataSource().getConnection();}catch(Exceptione){logger.error("获取数据库连接失败!",e);thrownewSQLException("获取数据库连接失败!");}}/***开启事务1.获取一个Connection,设置它的setAutoComnmit(false)*2.还要保证dao中使用的连接是我们刚刚创建的!--------------*3.创建一个Connection,设置为手动提交*4.把这个Connection给dao用!*5.还要让commitTransaction或rollbackTransaction可以获取到!**@throwsSQLException*/publicstaticvoidbeginTransaction()throwsException{try{Connectioncon=tl.get();if(con!=null){con.close();tl.remove();//thrownewSQLException("已经开启了事务,就不要重复开启了!");}con=getConnection();con.setAutoCommit(false);tl.set(con);}catch(Exceptione){logger.error("数据库事物开启失败!",e);thrownewSQLException("数据库事物开启失败!");}}/***提交事务1.获取beginTransaction提供的Connection,然后调用commit方法**@throwsSQLException*/publicstaticvoidcommitTransaction()throwsSQLException{Connectioncon=tl.get();try{if(con==null)thrownewSQLException("还没有开启事务,不能提交!");con.commit();}catch(Exceptione){logger.error("数据库事物提交失败!",e);thrownewSQLException("数据库事物提交失败!");}finally{if(con!=null){con.close();}tl.remove();}}/***回滚事务1.获取beginTransaction提供的Connection,然后调用rollback方法**@throwsSQLException*/publicstaticvoidrollbackTransaction()throwsSQLException{Connectioncon=tl.get();try{if(con==null)thrownewSQLException("还没有开启事务,不能回滚!");con.rollback();}catch(Exceptione){logger.error("数据库事物回滚失败!",e);thrownewSQLException("数据库事物回滚失败!");}finally{if(con!=null){con.close();}tl.remove();}}/***释放连接*@paramconnection*@throwsSQLException*/publicstaticvoidreleaseConnection(Connectionconnection)throwsSQLException{try{Connectioncon=tl.get();//判断它是不是事务专用,如果是,就不关闭!如果不是事务专用,那么就要关闭!//如果con==null,说明现在没有事务,那么connection一定不是事务专用的!//如果con!=null,说明有事务,那么需要判断参数连接是否与con相等,若不等,说明参数连接不是事务专用连接if(con==null||con!=connection)connection.close();}catch(Exceptione){logger.error("数据库连接释放失败!",e);thrownewSQLException("数据库连接释放失败!");}}}

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

publicclassDBCPTest{privatestaticfinalintsumCount=1000000;privatestaticfinalintthreadNum=600;privatevoidbefore(Stringpath){SysConstants.putValue(path);newDBCPService().insert("deletefromt_test");}@TestpublicvoidtestMysql(){longstart=System.currentTimeMillis();Stringpath="config/mysql/dbcp-jdbc.properties";before(path);for(inti=0;i<1;i++){Stringsql="insertintot_test(id,name)values('"+i+"','dbcp-mysql-"+i+"')";newDBCPService().insert(sql);}System.out.println("耗时:"+(System.currentTimeMillis()-start));}@TestpublicvoidtestThreadMysql()throwsInterruptedException{Stringpath="config/mysql/dbcp-jdbc.properties";before(path);BlockingQueue<String>queue=newLinkedBlockingQueue<String>();for(inti=0;i<sumCount;i++){Stringsql="insertintot_test(id,name)values('"+i+"','dbcp-mysql-"+i+"')";queue.put(sql);}longstart=System.currentTimeMillis();finalCountDownLatchcountDownLatch=newCountDownLatch(threadNum);for(inti=0;i<threadNum;i++){finalintfinalI=i+1;newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("thread"+finalI+"start");booleanisGo=true;while(isGo){Stringsql=queue.poll();if(sql!=null){newDBCPService().insert(sql);}else{isGo=false;System.out.println("thread"+finalI+"finish");countDownLatch.countDown();}}}}).start();}countDownLatch.await();System.out.println("耗时:"+(System.currentTimeMillis()-start));}}

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

三、性能测试

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

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

3.1、插入10万条数据

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

测试dbcp执行结果

测试c3p0执行结果

测试druid执行结果

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

从数据连接池性能角度看:dbcp >= druid > c3p0

从数据库性能角度看:oracle > postgresql > mysql

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

3.2、插入100万条数据

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

测试dbcp执行结果

测试c3p0执行结果

测试druid执行结果

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

从数据连接池性能角度看:druid性能比较稳定,dbcp、c3p0都有某种程度的执行失败

从数据库性能角度看:postgresql > oracle > mysql

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

四、小结

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

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

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

感谢各位的阅读,以上就是“数据库连接池的方式有哪几种”的内容了,经过本文的学习后,相信大家对数据库连接池的方式有哪几种这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!