11.1 数据库连接池
11.1.1 什么是数据库连接池
在JDBC 编程中,每次创建和断开Connection 对象都会消耗一定的时间和IO资源。在Java程序与数据 库之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,Java程序则要把代表连接 的java.sql.Connection 对象等加载到内存中,所以建立数据库连接的开销很大,尤其是有大量的并发访问时。 假如某网站一天的访问量是10万,那么该网站的服务器就需要创建、断开连接10万次,频繁地创建、断开 数据库连接会影响数据库的访问效率,甚至导致数据库崩溃。
为了避免频繁地创建数据库连接,数据库连接池技术应运而生。数据库连接池负责分配、管理和释放数 据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立。简单地说,数据库连接池就是为 数据库建立的一个“缓冲池”。预先在“缓冲池”中放入一定数量的连接,当需要建立数据库连接时,只需 要从“缓冲池”中取出一个,使用完毕后再放回“缓冲池”即可。下面通过一张图简单描述应用程序通过连 接池连接数据库的原理。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,当应用程序访问 数据库时并不是直接创建 Connection,而是向连接池“申请”一个 Connection。如果连接池中有空闲的 Connection,则返回一个连接给应用程序,否则应用程序需要创建新的Connection。使用完毕后,连接池会将 Connection 回收,重新放入连接池以供其他的线程使用,从而减少创建和断开数据库连接的次数,提高数据 库的访问效率。
11.1.2 DataSource 接口
为了获取数据库连接对象(Connection),JDBC 提供了 javax.sql.DataSource 接口,javax.sql.DataSource 接 口负责与数据库建立连接,并定义了返回值为Connection对象的方法,具体如下:
Connection getConnection( )
Connection getConnection(String username, String password)
11.1.3 DBCP数据库连接池
DBCP 即数据库连接池(DataBase Connection Pool),是 Apache 组织下的开源连接池实现,也是Tomcat 服务器使用的连接池组件。使用DBCP数据库连接池时,需要在应用程序中导入以下两个JAR包:
1. commons-dbcp2.jar包
2. commons-pool2.jar包
当使用DBCP数据库连接池时,首先要创建数据源对象,数据源对象的创建方式有两种,具体如下:
1. 通过BasicDataSource类直接创建数据源对象
在IDEA中创建一个名称为chapter11的Web项目,在项目chapter11中导入mysql-connector-java- 8.0.15.jar、commons-dbcp2-2.7.0.jar、commons-logging-1.2.jar和commons-pool2-2.8.0.jar共4个JAR包,并发布 到类路径下;然后在项目的src目录下创建包cn.itcast.chapter11.example,并在该包下创建一个Example01类, 该类采用手动方式获取数据库的连接信息和数据源的初始化信息。
Example01.java
package cn.itcast.chapter11.example; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSource; public class Example01 { public static DataSource ds = null; static { // 获取DBCP数据库连接池实现类对象 BasicDataSource bds = new BasicDataSource(); // 设置连接数据库需要的配置信息 bds.setDriverClassName("com.mysql.cj.jdbc.Driver"); bds.setUrl("jdbc:mysql://localhost:3306/jdbc?serverTimezone=GMT%2B8"); bds.setUsername("root"); bds.setPassword("123456"); // 设置连接池的初始化连接参数 bds.setInitialSize(5); ds = bds; } public static void main(String[] args) throws SQLException { // 获取数据库连接对象 Connection conn = ds.getConnection(); //获取数据库连接信息 DatabaseMetaData metaData = conn.getMetaData(); //打印数据库连接信息 System.out.println(metaData.getURL() +",UserName="+metaData.getUserName() +","+metaData.getDriverName()); } }
运行main()方法:
2. 通过读取配置文件创建数据源对象
除了使用BasicDataSource直接创建数据源对象外,还 可以使用BasicDataSourceFactory工厂类读取配置文件,创建数据源对象,然后获取数据库连接对象。下面通 过一个案例演示通过读取配置文件创建数据源对象。
在chapter11项目的src目录下创建dbcpconfig.properties文件,该文件用于设置数据库的连接信息和数据 源的初始化信息。
dbcpconfig.properties
#Á¬½ÓÉèÖà driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbc?serverTimezone=GMT%2B8 username=root password=root #³õʼ»¯Á¬½Ó initialSize=5 #×î´ó¿ÕÏÐÁ¬½Ó maxIdle=10
在cn.itcast.chapter11.example包下创建一个Example02类,Example02类从配置文件中获取数据库的连接 信息和数据源的初始化信息。
Example02.java
package cn.itcast.chapter11.example; import java.io.InputStream; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSourceFactory; public class Example02 { public static DataSource ds = null; static { // 新建一个配置文件对象 Properties prop = new Properties(); try { // 通过类加载器找到文件路径,读取配置文件 InputStream in = new Example02().getClass().getClassLoader() .getResourceAsStream("dbcpconfig.properties"); // 把文件以输入流的形式加载到配置对象中 prop.load(in); // 创建数据源对象 ds = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static void main(String[] args) throws SQLException { // 获取数据库连接对象 Connection conn = ds.getConnection(); //获取数据库连接信息 DatabaseMetaData metaData = conn.getMetaData(); //打印数据库连接信息 System.out.println(metaData.getURL() +",UserName="+metaData.getUserName() +","+metaData.getDriverName()); } }
运行main()方法:
11.1.4 C3P0数据库连接池
当使用 C3P0 数据库连接池时,首先需要创建数据源对象,创建数据源对象可以通过调用 ComboPooledDataSource 类的构造方法实现。ComboPooledDataSource 类有两个构造方法,分别是ComboPooled- DataSource( )和 ComboPooledDataSource(String configName)。下面分别进行介绍。
1. 通过ComboPooledDataSource( )构造方法创建数据源对象
调用ComboPooledDataSource( )构造方法创建数据源对象,需要手动给数据源对象设置属性值,然后获取 数据库连接对象。
在项目chapter11 中导入 JAR 包 c3p0-0.9.2.1.jar 和 mchange-commons-java-0.2.3.4.jar,然后在 cn.itcast. chapter11.example 包下创建一个 Example03 类,Example03 类采用 C3P0 数据库连接池,并使用C3P0 数据库 连接池对象获取Connection对象。Example03 类的实现如文件11-4所示。
Example03.java
package cn.itcast.chapter11.example; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class Example03 { public static DataSource ds = null; // 初始化C3P0数据库连接池 static { ComboPooledDataSource cpds = new ComboPooledDataSource(); // 设置连接数据库需要的配置信息 try { cpds.setDriverClass("com.mysql.cj.jdbc.Driver"); cpds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc?" + "serverTimezone=GMT%2B8"); cpds.setUser("root"); cpds.setPassword("123456"); //设置连接池的参数 cpds.setInitialPoolSize(5); cpds.setMaxPoolSize(15); ds = cpds; } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static void main(String[] args) throws SQLException { // 获取数据库连接对象 System.out.println(ds.getConnection()); } }
运行main( )方法:
2. 通过ComboPooledDataSource(String configName)构造方法创建数据源对象
调用ComboPooledDataSource(String configName)构造方法可以读取c3p0-config.xml配置文件,再根据 配置文件中的配置信息创建数据源对象,然后获取数据库连接对象。下面通过一个案例演示根据读取的配置 文件创建数据源对象。
在chapter11项目的src根目录下创建名称为c3p0-config的XML文件,用于设置数据库的连接信息和数 据源的初始化信息。
c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl"> jdbc:mysql://localhost:3306/jdbc?serverTimezone=GMT%2B8 </property> <property name="user">root</property> <property name="password">root</property> <property name="checkoutTimeout">30000</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </default-config> <named-config name="itcast"> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl"> jdbc:mysql://localhost:3306/jdbc?serverTimezone=GMT%2B8 </property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">5</property> <property name="maxPoolSize">15</property> </named-config> </c3p0-config>
注:需要将密码改成自己数据库的密码,前面同样
在cn.itcast.chapter11.example 包下创建一个 Example04 类,在该类中使用 C3P0 数据库连接池从配置文件 中获取Connection 对象。
Example04.java
package cn.itcast.chapter11.example; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class Example04 { public static DataSource ds = null; // 初始化C3P0数据库连接池 static { // 使用c3p0-config.xml配置文件中的named-config节点中name属性的值 ComboPooledDataSource cpds = new ComboPooledDataSource("itcast"); ds = cpds; } public static void main(String[] args) throws SQLException { System.out.println(ds.getConnection()); } }
运行main( )方法 :
11.2 DBUtils 工具
11.2.1 DBUtils 工具介绍
为了更加简单地使用JDBC,Apache 组织提供了一个DBUtils 工具,它是操作数据库的一个组件,实现 了对JDBC的简单封装,可以在不影响数据库访问性能的情况下简化JDBC的编码工作量。DBUtils工具要有 三个作用。
- 写数据。
- 读数据
- 优化性能
11.2.2 DBUtils 类
11.2.3 QueryRunner类
QueryRunner 类简化了执行SQL 语句的代码,它与ResultSetHandler 配合就能完成大部分的数据库操作, 大大减少了编码量。
11.2.4 ResultSetHandler 接口
ResultSetHandler 接口用于处理结果集ResultSet,它可以将结果集中的数据转换为不同的形式。根据结果 集中不同的数据类型,ResultSetHandler 提供了几种常见的实现类。
另外,ResultSetHandler 接口还提供了一个单独的方法handle (java.sql.ResultSet rs),如果上述实现类没有 提供想要的功能,可以自定义一个实现ResultSetHandler接口的类,然后通过重写handle( )方法,实现结果集 的处理。
11.2.5 ResultSetHandler 实现类
1. BeanHandler和BeanListHandler
BeanHandler 和 BeanListHandler 实现类是将结果集中的数据封装到对应的JavaBean 中。在封装时,表中 数据的字段和 JavaBean 的属性是相互对应的,一条数据记录被封装进一个对应的 JavaBean 对象中。
(1)在名为jdbc的数据库中创建数据表user,创建语句如下:
USE jdbc;
CREATE TABLE user(
id INT(3) PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
password VARCHAR(20) NOT NULL
);
向user 表插入三条数据,具体语句如下:
INSERT INTO user(name,password) VALUES ('zhangsan','123456');
INSERT INTO user(name,password) VALUES ('lisi','123456');
INSERT INTO user(name,password) VALUES ('wangwu','123456');
(2)将下载的DBUtils工具的JAR包commons- dbutils-1.7.jar添加到项目的lib目录中,并将第10章 中文件10-5(JDBCUtils.java)复制到cn.itcast.chapter11. example包下。
(3)在chapter11项目的cn.itcast.chapter11.example 包中创建一个名为BaseDao的类,在该类中编写一个 通用的查询方法。
BaseDao.java
package cn.itcast.chapter11.example; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.commons.dbutils.ResultSetHandler; public class BaseDao { // 优化查询 public static Object query(String sql, ResultSetHandler<?> rsh, Object... params) throws SQLException { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 获得连接 conn = JDBCUtils.getConnection(); // 预编译sql pstmt = conn.prepareStatement(sql); // 将参数设置进去 for (int i = 0; params != null && i < params.length; i++) { pstmt.setObject(i + 1, params[i]); } // 发送sql rs = pstmt.executeQuery(); // 让调用者去实现对结果集的处理 Object obj = rsh.handle(rs); return obj; } catch (Exception e) { e.printStackTrace(); }finally { // 释放资源 JDBCUtils.release(rs, pstmt, conn); } return rs; } }
(4)在cn.itcast.chapter11.example包下创建实体类User,用于封装User对象。
User.java
package cn.itcast.chapter11.example; public class User { private int id; private String name; private String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
(5)在cn.itcast.chapter11.example包下创建类ResultSetTest1,用于演示BeanHandler类对结果集的处理。
ResultSetTest1.java
package cn.itcast.chapter11.example; import java.sql.SQLException; import org.apache.commons.dbutils.handlers.BeanHandler; public class ResultSetTest1 { public static void testBeanHandler() throws SQLException { BaseDao basedao = new BaseDao(); String sql = "select * from user where id=?"; User user = (User) basedao.query(sql, new BeanHandler(User.class), 1); System.out.print("id为1的User对象的name值为:" + user.getName()); } public static void main(String[] args) throws SQLException { testBeanHandler(); } }
(6)运行mian()方法。
(7)在cn.itcast.chapter11.example包下创建ResultSetTest2类,用于演示BeanListHandler类对结果集的处理。
ResultSetTest2.java
package cn.itcast.chapter11.example; import java.sql.SQLException; import java.util.ArrayList; import org.apache.commons.dbutils.handlers.BeanListHandler; public class ResultSetTest2 { public static void testBeanListHandler() throws SQLException { BaseDao basedao = new BaseDao(); String sql = "select * from user "; ArrayList<User> list = (ArrayList<User>) basedao.query(sql, new BeanListHandler(User.class)); for (int i = 0; i < list.size(); i++) { System.out.println("第" + (i + 1) + "条数据的username值为:" + list.get(i).getName()); } } public static void main(String[] args) throws SQLException { testBeanListHandler(); } }
(8)运行mian()方法:
2. ColumnListHandler 和ScalarHandler
在使用DBUtils 工具操作数据库时,如果需要输出结果集中所有数据的值,可以使用ColumnListHandler 类。下面通过一个案例演示ColumnListHandler类的使用。
(1)在cn.itcast.chapter11.example 包下创建 ResultSetTest3 类,用于演示 ColumnListHandler 类的使用方法。
ResultSetTest3.java
package cn.itcast.chapter11.example; import java.sql.SQLException; import org.apache.commons.dbutils.handlers.ColumnListHandler; public class ResultSetTest3 { public static void testColumnListHandler() throws SQLException { BaseDao basedao = new BaseDao(); String sql = "select * from user"; Object arr = (Object) basedao.query(sql, new ColumnListHandler("name")); System.out.println(arr); } public static void main(String[] args) throws SQLException { testColumnListHandler(); } }
(2)运行main()方法,控制台输出:
在使用 DBUtils 工具操作数据库时,如果需要输出结果集中一行数据的指定字段值,可以使用 ScalarHandler 类。
(1)在 cn.itcast.chapter11.example 包下创建 ResultSetTest4 类,用于演示 ScalarHandler 类的使用方法。
ResultSetTest4.java
package cn.itcast.chapter11.example; import java.sql.SQLException; import org.apache.commons.dbutils.handlers.ScalarHandler; public class ResultSetTest4 { public static void testScalarHandler() throws SQLException { BaseDao basedao = new BaseDao(); String sql = "select * from user where id=?"; Object arr = (Object) basedao.query(sql, new ScalarHandler("name"), 1); System.out.println(arr); } public static void main(String[] args) throws SQLException { testScalarHandler(); } }
(2)运行main()方法,控制台输出:
11.2.6 动手实践:使用DBUtils实现增删改查
1. 搭建开发环境
本任务使用11.2.5小节在jdbc数据库中创建的user表作为数据表,使用DBUtils工具对user表进行增删改查操作。
2. 创建JavaBean
本任务使用文件11-8 作为 JavaBean,在项目 chapter11 的目录下,创建一个名为 cn.itcast.jdbc.javabean 的包,将文件11-8复制到该包下。
3. 创建C3p0Utils工具类
C3p0Utils.java
package cn.itcast.jdbc.utils; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class C3p0Utils { private static DataSource ds; static { ds = new ComboPooledDataSource(); } public static DataSource getDataSource(){ return ds; } }
4. 创建InsertDao类,完成插入操作
在项目chapter11的src目录下,创建一个名为cn.itcast.jdbc.dao的包,然后在该包下创建一个InsertDao 类,实现对user表插入数据的操作。
InsertDao.java
package cn.itcast.jdbc.dao; import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner; import cn.itcast.jdbc.javabean.User; import cn.itcast.jdbc.utils.C3p0Utils; public class InsertDao { public static void main(String[] args)throws SQLException{ // 创建QueryRunner对象 QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource()); String sql = "insert into user (name,password) values ('hello1',123456)"; int num = runner.update(sql); if (num > 0){ System.out.println("添加成功!"); }else{ System.out.println("添加失败!"); } } }
运行:
向MySQL数据库发送查询语句,查询结果如图:
5. 创建UpdateDao类,完成修改操作
UpdateDao.java
package cn.itcast.jdbc.dao; import cn.itcast.jdbc.javabean.User; import cn.itcast.jdbc.utils.C3p0Utils; import org.apache.commons.dbutils.QueryRunner; import java.sql.SQLException; public class UpdateDao { public static void main(String[] args)throws SQLException { // 创建QueryRunner对象 QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource()); // 写SQL语句 String sql = "update user set name='hello2',password=111111 where name='hello1'"; // 调用方法 int num = runner.update(sql); if (num > 0){ System.out.println("修改成功!"); }else{ System.out.println("修改失败!"); } } }
运行结果如图:
在命令行向MySQL数据库发送查询语句,查询结果如图:
6. 创建DeleteDao类,完成删除操作
DeleteDao.java
package cn.itcast.jdbc.dao; import cn.itcast.jdbc.utils.C3p0Utils; import org.apache.commons.dbutils.QueryRunner; import java.sql.SQLException; public class DeleteDao { public static void main(String[] args)throws SQLException { // 创建QueryRunner对象 QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource()); // 写SQL语句 String sql = "delete from user where name='hello2'"; // 调用方法 int num = runner.update(sql); if (num > 0){ System.out.println("删除成功!"); }else{ System.out.println("删除失败!"); } } }
运行结果如图:
数据修改成功。在命令行向MySQL数据库发送查询语句,查询结果如图:
7. 创建QueryDao类,完成查询操作
QueryDao.java
package cn.itcast.jdbc.dao; import cn.itcast.jdbc.javabean.User; import cn.itcast.jdbc.utils.C3p0Utils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import java.sql.SQLException; import java.util.List; public class QueryDao { public static void main(String[] args)throws SQLException { // 创建QueryRunner对象 QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource()); // 写SQL语句 // String sql = "select * from user"; String sql = "select * from user where id=2"; // 调用方法 // List<User> list = (List) runner.query(sql, new BeanListHandler(User.class)); // for(User user : list){ // System.out.println(user.getId()+","+user.getName()+","+user.getPassword()); // } User user = (User) runner.query(sql, new BeanHandler(User.class)); System.out.println(user.getId()+","+user.getName()+"," +user.getPassword()); } }
运行结果如图:
QueryDao.java
package cn.itcast.jdbc.dao; import cn.itcast.jdbc.javabean.User; import cn.itcast.jdbc.utils.C3p0Utils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import java.sql.SQLException; import java.util.List; public class QueryDao { public static void main(String[] args)throws SQLException { // 创建QueryRunner对象 QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource()); // 写SQL语句 String sql = "select * from user"; // String sql = "select * from user where id=2"; // 调用方法 List<User> list = (List) runner.query(sql, new BeanListHandler(User.class)); for(User user : list){ System.out.println(user.getId()+","+user.getName()+","+user.getPassword()); } // User user = (User) runner.query(sql, new BeanHandler(User.class)); // System.out.println(user.getId()+","+user.getName()+"," +user.getPassword()); } }
运行结果如图: