1. 基本概念
- 数据库连接池:负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。这项技术能明显节约资源和提高对数据库操作的性能。
- 原理:是一个存放数据库连接的容器(集合)。当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
2. javax.sql.DataSource接口
负责与数据库建立连接,返回一个连接对象Connection。
Connection getConnection()
:直接获取连接;Connection getConnection(String username, String password)
:根据登录信息获取连接。- DataSource接口由数据库供应商实现(C3P0、Druid):
Connection.close()
:归还连接。
数据库连接池实现类会重写该方法,使得从连接池中获取的连接对象Connection调用close()方法时不会再关闭连接,而是归还连接。
3. C3P0
开源数据库连接池。实现了DataSource接口,支持JDBC2、JDBC3标准规范,易于扩展性能优越,Hibernate、Spring使用的都是该数据源。
- 步骤:
-
导入2个jar包 :
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.12.jar
注:不要忘记导入数据库驱动jar包
-
定义配置文件:
- 名称:
c3p0.properties
或c3p0-config.xml
; - 路径:根目录下。
<c3p0-config> <!-- 使用默认的配置读取连接池对象 --> <default-config> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/db4</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池参数 --> <!--初始化申请的连接数量--> <property name="initialPoolSize">5</property> <!--最大的连接数量--> <property name="maxPoolSize">10</property> <!--超时时间--> <property name="checkoutTimeout">3000</property> </default-config> <!-- 使用其他连接池对象 --> <named-config name="otherc3p0"> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/db3</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">8</property> <property name="checkoutTimeout">1000</property> </named-config> </c3p0-config>
- 名称:
-
创建数据库连接池对象ComboPooledDataSource:
//获取默认配置的连接池对象 DataSource ds = new ComboPooledDataSource(); //获取指定的连接池对象 DataSource ds = new ComboPooledDataSource("otherc3p0");
-
获取连接getConnection:
Connection conn = ds.getConnection();
-
- 配置:
- 最大连接数maxPoolSize:
以上连接5与连接7为同一连接import com.mchange.v2.c3p0.ComboPooledDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class C3P0Demo { public static void main(String[] args) throws SQLException { //1.获取DataSource,使用默认配置 DataSource ds = new ComboPooledDataSource(); //2.获取连接 for (int i = 1; i <= 11; i++) { Connection conn = ds.getConnection(); System.out.println(i + ":" + conn); if (i == 5) { conn.close();//归还连接到连接池中 } } } } 输出结果: 1:com.mchange.v2.c3p0.impl.NewProxyConnection@6156496 [wrapping: com.mysql.jdbc.JDBC4Connection@3c153a1] 2:com.mchange.v2.c3p0.impl.NewProxyConnection@13acb0d1 [wrapping: com.mysql.jdbc.JDBC4Connection@3e3047e6] 3:com.mchange.v2.c3p0.impl.NewProxyConnection@2b6856dd [wrapping: com.mysql.jdbc.JDBC4Connection@5db45159] 4:com.mchange.v2.c3p0.impl.NewProxyConnection@7c417213 [wrapping: com.mysql.jdbc.JDBC4Connection@15761df8] 5:com.mchange.v2.c3p0.impl.NewProxyConnection@327b636c [wrapping: com.mysql.jdbc.JDBC4Connection@45dd4eda] 6:com.mchange.v2.c3p0.impl.NewProxyConnection@19d37183 [wrapping: com.mysql.jdbc.JDBC4Connection@1a0dcaa] 7:com.mchange.v2.c3p0.impl.NewProxyConnection@fdefd3f [wrapping: com.mysql.jdbc.JDBC4Connection@45dd4eda] 8:com.mchange.v2.c3p0.impl.NewProxyConnection@a4102b8 [wrapping: com.mysql.jdbc.JDBC4Connection@11dc3715] 9:com.mchange.v2.c3p0.impl.NewProxyConnection@7a52f2a2 [wrapping: com.mysql.jdbc.JDBC4Connection@78047b92] 10:com.mchange.v2.c3p0.impl.NewProxyConnection@79ca92b9 [wrapping: com.mysql.jdbc.JDBC4Connection@1460a8c0] 11:com.mchange.v2.c3p0.impl.NewProxyConnection@4387b79e [wrapping: com.mysql.jdbc.JDBC4Connection@6e75aa0d]
@45dd4eda
,说明连接被成功回收归还到连接池中。若不归还则会超出最大连接数,第11个连接在等待checkoutTimeout
长的时间后会报错:1:com.mchange.v2.c3p0.impl.NewProxyConnection@27082746 [wrapping: com.mysql.jdbc.JDBC4Connection@66133adc] 2:com.mchange.v2.c3p0.impl.NewProxyConnection@42f30e0a [wrapping: com.mysql.jdbc.JDBC4Connection@24273305] 3:com.mchange.v2.c3p0.impl.NewProxyConnection@46f5f779 [wrapping: com.mysql.jdbc.JDBC4Connection@1c2c22f3] 4:com.mchange.v2.c3p0.impl.NewProxyConnection@33e5ccce [wrapping: com.mysql.jdbc.JDBC4Connection@5a42bbf4] 5:com.mchange.v2.c3p0.impl.NewProxyConnection@52d455b8 [wrapping: com.mysql.jdbc.JDBC4Connection@4f4a7090] 6:com.mchange.v2.c3p0.impl.NewProxyConnection@6956de9 [wrapping: com.mysql.jdbc.JDBC4Connection@769c9116] 7:com.mchange.v2.c3p0.impl.NewProxyConnection@2d6d8735 [wrapping: com.mysql.jdbc.JDBC4Connection@ba4d54] 8:com.mchange.v2.c3p0.impl.NewProxyConnection@de0a01f [wrapping: com.mysql.jdbc.JDBC4Connection@4c75cab9] 9:com.mchange.v2.c3p0.impl.NewProxyConnection@6f79caec [wrapping: com.mysql.jdbc.JDBC4Connection@67117f44] 10:com.mchange.v2.c3p0.impl.NewProxyConnection@2471cca7 [wrapping: com.mysql.jdbc.JDBC4Connection@5fe5c6f] Exception in thread "main" java.sql.SQLException: An attempt by a client to checkout a Connection has timed out. at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:118) at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:77) at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:690) at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140) at xxx.xxx.C3P0Demo.main(C3P0Demo.java:14) Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@490d6c15 -- timeout at awaitAvailable() at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1467) at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:644) at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:554) at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse(C3P0PooledConnectionPool.java:758) at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:685) ... 2 more
- 使用指定连接池对象:此时最大连接数为8,第9个连接在等待 1秒后会报错:
import com.mchange.v2.c3p0.ComboPooledDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class C3P0Demo { public static void main(String[] args) throws SQLException { //1.获取DataSource,使用指定名称配置 DataSource ds = new ComboPooledDataSource("otherc3p0"); //2.获取连接 for (int i = 1; i <= 10; i++) { Connection conn = ds.getConnection(); System.out.println(i + ":" + conn); } } } 输出结果: 1:com.mchange.v2.c3p0.impl.NewProxyConnection@27082746 [wrapping: com.mysql.jdbc.JDBC4Connection@66133adc] 2:com.mchange.v2.c3p0.impl.NewProxyConnection@42f30e0a [wrapping: com.mysql.jdbc.JDBC4Connection@24273305] 3:com.mchange.v2.c3p0.impl.NewProxyConnection@46f5f779 [wrapping: com.mysql.jdbc.JDBC4Connection@1c2c22f3] 4:com.mchange.v2.c3p0.impl.NewProxyConnection@33e5ccce [wrapping: com.mysql.jdbc.JDBC4Connection@5a42bbf4] 5:com.mchange.v2.c3p0.impl.NewProxyConnection@52d455b8 [wrapping: com.mysql.jdbc.JDBC4Connection@4f4a7090] 6:com.mchange.v2.c3p0.impl.NewProxyConnection@6956de9 [wrapping: com.mysql.jdbc.JDBC4Connection@769c9116] 7:com.mchange.v2.c3p0.impl.NewProxyConnection@2d6d8735 [wrapping: com.mysql.jdbc.JDBC4Connection@ba4d54] 8:com.mchange.v2.c3p0.impl.NewProxyConnection@de0a01f [wrapping: com.mysql.jdbc.JDBC4Connection@4c75cab9] Exception in thread "main" java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
- 最大连接数maxPoolSize:
4. Druid
开源数据库连接池,由阿里巴巴提供。能够提供强大的监控和扩展功能。
-
步骤:
- 导入jar包:
druid-1.0.9.jar
; - 定义配置文件:任意名称的properties文件,可以放在任意目录下。
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///db3 username=root password=root # 初始化连接数量 initialSize=5 # 最大连接数 maxActive=10 # 最大等待时间 maxWait=3000
- 加载配置文件Properties;
- 通过工厂DruidDataSourceFactory来获取数据库连接池对象;
- 获取连接getConnection。
import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; public class DruidDemo { public static void main(String[] args) throws Exception { //1.加载配置文件 Properties pro = new Properties(); InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); //2.获取连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(pro); //3.获取连接 Connection conn = ds.getConnection(); System.out.println(conn); } } 输出结果: com.mysql.jdbc.JDBC4Connection@5d740a0f
- 导入jar包:
-
定义工具类JDBCUtils:
- 提供静态代码块加载配置文件,初始化连接池对象。
- 提供方法:
- 获取连接方法:通过数据库连接池获取连接;
- 释放资源;
- 获取连接池的方法。
package com.ernest.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class JDBCUtils { //1.定义成员变量 DataSource private static DataSource ds; static { try { //1.加载配置文件 Properties pro = new Properties(); pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties")); //2.获取DataSource ds = DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 获取连接池方法 */ public static DataSource getDataSource() { return ds; } /** * 获取连接 */ public static Connection getConnection() throws SQLException { return ds.getConnection(); } /** * 释放资源 */ public static void close(Statement stmt, Connection conn) { close(null, stmt, conn); } public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } } } }
测试类:
import com.ernest.utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class DruidDemo02 { public static void main(String[] args) { //完成添加操作:给account表添加一条记录 Connection conn = null; PreparedStatement pstmt = null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //2.定义sql String sql = "insert into account values(null,?,?)"; //3.获取pstmt对象 pstmt = conn.prepareStatement(sql); //4.给?赋值 pstmt.setString(1, "王五"); pstmt.setDouble(2, 3000); //5.执行sql int count = pstmt.executeUpdate(); System.out.println(count); } catch (SQLException e) { e.printStackTrace(); } finally { //6.释放资源 JDBCUtils.close(pstmt, conn); } } } 输出结果: 1
5. JdbcTemplate
- 概念:
Spring框架对JDBC做的封装,通过模板设计模式帮助我们消除了冗长的代码,在尽可能保留灵活性的情况下,简化了JDBC操作。可以看作是一个小型的轻量级持久化层框架,和DBUtils风格非常接近。 - 步骤:
- 导入jar包:
spring-tx-5.0.0.RELEASE.jar
spring-beans-5.0.0.RELEASE.jar
spring-core-5.0.0.RELEASE.jar
spring-jdbc-5.0.0.RELEASE.jar
commons-logging-1.2.jar
- 创建JdbcTemplate对象,依赖于数据源DataSource:
JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
- 调用JdbcTemplate的方法来完成CRUD的操作:
-
update(...)
:执行DML语句(增、删、改);// 增 String sql = "insert into emp(id,ename,dept_id) values(?,?,?)"; int count = template.update(sql, 1015, "郭靖", 10); System.out.println(count); // 删 String sql = "delete from emp where id = ?"; int count = template.update(sql, 1015); System.out.println(count); // 改 String sql = "update emp set salary = 10000 where id = 1001"; int count = template.update(sql); System.out.println(count);
-
queryForMap(...)
:查询结果将结果集封装为Map集合,将列名作为key,将值作为value 将这条记录封装为一个Map集合String sql = "select * from emp where id = ? or id = ?"; Map<String, Object> map = template.queryForMap(sql, 1001,1002); System.out.println(map);
注意:这个方法查询的结果集长度只能是1
-
queryForList(...)
:查询结果将结果集封装为list集合String sql = "select * from emp"; List<Map<String, Object>> list = template.queryForList(sql); for (Map<String, Object> stringObjectMap : list) { System.out.println(stringObjectMap); }
注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
-
query(...)
:查询结果,将结果封装为JavaBean对象,参数为RowMapperString sql = "select * from emp"; List<Emp> list = template.query(sql, new RowMapper<Emp>() { @Override public Emp mapRow(ResultSet rs, int i) throws SQLException { Emp emp = new Emp(); int id = rs.getInt("id"); String ename = rs.getString("ename"); int job_id = rs.getInt("job_id"); int mgr = rs.getInt("mgr"); Date joindate = rs.getDate("joindate"); double salary = rs.getDouble("salary"); double bonus = rs.getDouble("bonus"); int dept_id = rs.getInt("dept_id"); emp.setId(id); emp.setEname(ename); emp.setJob_id(job_id); emp.setMgr(mgr); emp.setJoindate(joindate); emp.setSalary(salary); emp.setBonus(bonus); emp.setDept_id(dept_id); return emp; } }); for (Emp emp : list) { System.out.println(emp); }
一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装,参数为new BeanPropertyRowMapper<类型>(类型.class)
String sql = "select * from emp"; List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class)); for (Emp emp : list) { System.out.println(emp); }
-
queryForObject
:查询结果,将结果封装为对象,一般用于聚合函数的查询String sql = "select count(id) from emp"; Long total = template.queryForObject(sql, Long.class); System.out.println(total);
-
- 导入jar包: