Java 学习使用常见的开源连接池
目录
DBCP(DataBase Connection Pool)
连接池介绍
在说连接池之前,我们先想一个问题:程序要进行数据库操作,与数据库建立的是什么连接?开销怎么样?数据库是否可以同时支持上百万个连接?
首先第一个问题:程序与数据库建立的是socket连接,走的是传输层,使用TCP。
第二个问题:总开销 约等于 程序运行耗时 + 网络io + 数据库运行耗时。
第三个问题:应该是不能同时支持上百万个连接,几千个应该就是上限了。
看了上面三个问题,再提出一个问题:上面的问题中,我们可以优化哪一个环节?
运维可以优化网络传输的问题;DBA可以优化数据库的运行性能。
对于开发人员来说,我们可以优化第一个问题;首先,创建socket连接真的很耗时,主要的原因是因为建立TCP连接时有个3次握手,建立连接之后传输数据开销其实并不大。所以我们可以在这个角度上进行优化:尽量让程序与数据库建立几个连接,不要让程序频繁与数据库建立连接。
为了业务的正常执行,线程需要与数据库进行交互,只建立几个数据库连接,好像不现实。
我们可以换个方式来优化:创建固定数量的数据库的连接,这些数据库连接在Java中就是一个个对象而已,我们可以将这些对象存到容器中(比如List中),这个容器就叫做连接池。
当有一个线程需要与数据库交互的时候,如果容器中还有数据库连接对象,那就从容器中取出一个连接对象,使用完之后,并不关闭数据库连接,而是将数据库连接对象放回容器,方便其他线程使用;如果线程需要与数据库交互时,容器中没有数据库连接对象了,那么这个线程他就阻塞一下,等待别的线程使用完连接之后归还,然后再使用别的线程归还的数据库连接。
自定义连接池
自定义连接池的有个规范,需要实现DataSource接口,并且重写多个方法,但是主要重写一个无参的getConnection方法:
package cn.ganlixin.utils; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.logging.Logger; import javax.sql.DataSource; /** * 自定义的超简单的连接池,主要重写了DataSource接口中的getConnection方法,然后创建了归还连接的方法 */ public class MyConnectionPool implements DataSource { // 创建一个同步的容器来保存数据库连接 private static List<Connection> connectionList = Collections.synchronizedList(new LinkedList<Connection>()); // 设置建立数据库连接的最大数 private static final int MAX_CONNECTIONS = 50; // 编写静态初始化块,读取数据库配置文件,建立数据库连接,放入容器 static { InputStream _is = MyConnectionPool.class.getClassLoader().getResourceAsStream("database.properties"); try { Properties props = new Properties(); props.load(_is); String driver = props.getProperty("jdbc.driver"); String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); Class.forName(driver); // 创建多个连接,放入容器中 for (int i = 0; i < MAX_CONNECTIONS; i++) { Connection conn = DriverManager.getConnection(url, username, password); connectionList.add(conn); System.out.println("创建第 " + (i+1) + " 个连接"); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } // 从容器中取出一个数据库连接对象 @Override public Connection getConnection() throws SQLException { if (connectionList.size() > 0) { // 如果容器中还有数据库连接对象,就将取出第一个对象,并将该对象从容器中删除 Connection conn = connectionList.remove(0); System.out.println("使用了一个数据库连接, 容器中还剩下 " + connectionList.size() + " 个数据库连接"); return conn; } return null; } /** * 归还数据库连接给容器,便于其他线程使用 * @param conn 要归还的数据库连接对象 */ public void releaseConnection(Connection conn) { connectionList.add(conn); } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } }
测试
package cn.ganlixin.test; import java.sql.Connection; import java.sql.SQLException; public class TestPool { public static void main(String[] args) throws SQLException { MyConnectionPool connectionPool = new MyConnectionPool(); // 获取连接池 for (int i = 0; i < 20; i++) { // 获取连接 Connection connection = connectionPool.getConnection(); if (i % 3 == 0) { // 归还连接 connectionPool.releaseConnection(connection); } System.out.println(connection); } } }
JDBC Tomcat Pool
tomcat服务器可以提供数据库连接池。我们可以将数据库连接池的配置保存在一个文件名为context.xml的文件中。
context.xml可以放在项目下的webRoot/META-INF目录下,只对于本项目有效。
context.xml可以放在tomcat服务器安装路径的conf目录下,针对所有项目都有效。
<?xml version="1.0" encoding="UTF-8"?> <Context> <WatchedResource>WEB-INF/web.xml</WatchedResource> <Resource driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test" user="root" password="123456" name="tomcat_supported_pool" auth="Container" maxActive="50" maxIdle="20" maxWait="10000" type="javax.sql.DataSource" ></Resource> </Context>
测试:
package lixin.gan.test; import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.sql.DataSource; /** * Servlet implementation class TestPool */ @WebServlet("/TestPool") public class TestPool extends HttpServlet { @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html; charset=utf-8"); try { Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/tomcat_supported_pool"); Connection conn = ds.getConnection(); /* 进行数据库操作即可 */ } catch (NamingException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } }
DBCP(DataBase Connection Pool)
需要下载commons-dbcp.jar、commons-pool.jar、commons-logging.jar。另外仍旧需要导入mysql的驱动包。
下载网址(下载xxx-bin.zip即可):
http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi
http://commons.apache.org/proper/commons-pool/download_pool.cgi
http://commons.apache.org/proper/commons-logging/download_logging.cgi
下载之后解压,将jar包添加到build path中。
使用DBCP的示例:
package cn.ganlixin.test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import org.apache.commons.dbcp2.BasicDataSource; public class TestDBCP { public static void main(String[] args) throws SQLException { // 获取连接池 BasicDataSource dataSource = new BasicDataSource(); // 设置连接池相关信息 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); dataSource.setInitialSize(5); dataSource.setMinIdle(2); // 获取数据库连接 Connection conn = dataSource.getConnection(); // 执行数据库操作 Statement stmt = conn.createStatement(); stmt.executeUpdate("update stu set age = 30 where id < 4"); // 这个close()方法被重写了,并不是关闭数据库连接,而是将连接归还连接池 conn.close(); } }
使用配置文件来设置DBCP
前面使用DBCP时,是在程序中手动指定连接信息,同样的,我们可以使用配置文件保存DBCP的连接信息。
操作:在src下创建一个dbcp.properties文件,内容如下:
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test username=root password=root initialSize=20 maxIdle=10 minIdle=5 maxWait=10000
使用DBCP
package cn.ganlixin.test; import java.io.InputStream; import java.sql.Connection; import java.sql.Statement; import java.util.Properties; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSourceFactory; public class TestPool { public static void main(String[] args) throws Exception { BasicDataSourceFactory dataSourceFacoty = new BasicDataSourceFactory(); InputStream _is = TestPool.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties props = new Properties(); props.load(_is); // 获取连接池 BasicDataSource dataSource = dataSourceFacoty.createDataSource(props); Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); stmt.executeUpdate("update stu set age = 30 where id < 4"); conn.close(); } }
DBCP返回的数据库连接是包装类对象
当我们从DBCP连接池获取的连接其实是一个经过包装之后的数据库连接对象,而不是原生的jdbc数据库连接对象:
package cn.ganlixin.test; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSourceFactory; import org.apache.commons.dbcp2.DelegatingConnection; public class TestDBCP1 { public static void main(String[] args) throws Exception { BasicDataSourceFactory dataSourceFacoty = new BasicDataSourceFactory(); InputStream _is = TestPool.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties props = new Properties(); props.load(_is); BasicDataSource dataSource = dataSourceFacoty.createDataSource(props); // 获取数据库连接 Connection conn = dataSource.getConnection(); System.out.println(conn.getClass().getName()); // org.apache.commons.dbcp2.PoolingDataSource$PoolGuardConnectionWrapper // 通过DBCP连接池获得的是一个包装过后的连接池对象 // 可以通过下面的步骤获取原生的Connection对象 DelegatingConnection dc = (DelegatingConnection) conn; Connection connection = dc.getInnermostDelegateInternal(); System.out.println(connection.getClass().getName()); // com.mysql.jdbc.JDBC4Connection } }
C3P0
C3P0也是一个数据库连接池的开源项目,他和DBCP功能类似,但是有一些区别:
1、DBCP不会自动回收空闲连接的功能,而C3P0有这个功能。
2、DBCP需要手动指定配置文件的路径以及文件明,而C3P0不需要(C3P0的配置文件指定路径和名称)。
需要下载c3p0.jar,mchange-commons-java.jar,以及mysql的驱动包。
http://central.maven.org/maven2/com/mchange/c3p0/0.9.5.4/c3p0-0.9.5.4.jar
创建C3P0的配置文件
C3P0的配置文件名为c3p0-config.xml,放在src目录下即可,配置文件的内容如下:
<?xml version="1.0" encoding="utf-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">30</property> <property name="maxIdleTime">60</property> <property name="maxPoolSize">60</property> <property name="minPoolSize">15</property> </default-config> <!-- 可以指定两个数据库配置,一个线上环境,一个开发环境 --> <named-config name="dev"> <property name=""></property> <property name=""></property> <property name=""></property> <property name=""></property> <property name=""></property> <property name=""></property> <property name=""></property> <property name=""></property> <property name=""></property> </named-config> </c3p0-config>
测试C3P0
package cn.ganlixin.test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import com.mchange.v2.c3p0.ComboPooledDataSource; public class TestC3P0 { public static void main(String[] args) throws SQLException { // 获取连接池 // 加载默认的数据库连接配置 // ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 使用c3p0配置文件中named-config里面name为dev的数据库配置 ComboPooledDataSource dataSource = new ComboPooledDataSource("dev"); // 获取数据库连接 Connection connection = dataSource.getConnection(); // 获取到数据库连接之后就可以进行各种操作了 // code // c3p0返回的数据库连接同样是包装类 System.out.println(connection); // com.mchange.v2.c3p0.impl.NewProxyConnection@769c9116 // [wrapping: com.mysql.jdbc.JDBC4Connection@6aceb1a5] // 将数据库连接归还连接池 connection.close(); } }
Druid
Druid是alibaba开源的一个连接池项目;
使用Druid,需要下载druid.jar以及mysql的驱动包;
druid.jar的下载地址:http://central.maven.org/maven2/com/alibaba/druid/1.1.15/druid-1.1.15.jar
github地址:https://github.com/alibaba/druid
使用方法设置连接池信息
package cn.ganlixin.test; import java.sql.SQLException; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidPooledConnection; public class TestDruid { public static void main(String[] args) throws SQLException { // 获取连接池对象 DruidDataSource dataSource = new DruidDataSource(); // 设置连接信息 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 设置连接池的配置 dataSource.setInitialSize(20); dataSource.setMaxActive(30); dataSource.setMaxWait(1000); dataSource.setMinIdle(10); // 获取连接(得到的不是原生的jdbc for mysql的连接对象) DruidPooledConnection connection = dataSource.getConnection(); // 释放连接到连接池中 connection.close(); } }
使用properties配置文件的方式配置Druid
配置Druid可以参考官方的示例:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE
在src下创建druid-config.properties(文件名随意),内容如下:
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test username=root password=root maxActive=10 initialSize=5 maxWait=10000 minIdle=5
配置项和DBCP几乎一样。
进行测试:
package cn.ganlixin.test; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; import javax.sql.DataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; public class TestDruid2 { public static void main(String[] args) throws Exception { InputStream _is = TestDruid2.class.getClassLoader().getResourceAsStream("druid-config.properties"); Properties props = new Properties(); props.load(_is); /** 读取配置文件,手动调用setter进行设置连接池 DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(props.getProperty("driverClassName")); ......... */ // 利用工厂加载配置文件来配置连接池 DruidDataSourceFactory factory = new DruidDataSourceFactory(); DataSource dataSource = factory.createDataSource(props); // 获取数据库连接 Connection connection = dataSource.getConnection(); System.out.println(connection.getClass().getName()); connection.close(); } }