Mysql——DbUtils的使用
一、JDBC复习
作业:在 MySQL 中,创建以下数据表,使用 JDBC 连接 MySQL 数据库。同时,实现数据表的增、删、改、查(根据ID查询、分页查询)操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | drop database if exists jdbc_test; create database if not exists jdbc_test ; use jdbc_test; create table `staffs`( `id` int(10) primary key auto_increment, `name` varchar(24) not null comment '姓名', `age` int(10) default 18 comment '年龄', `phone` char(11) comment '联系方式', `sta_pos` varchar(20) comment '职位', `add_time` datetime default current_timestamp comment '入职时间', `update_time` datetime default current_timestamp comment '更新时间' ) engine=innodb charset=utf8 comment '员工记录表'; # 提示:需要自行下载连接MySQL的驱动程序 insert into staffs(name,age,phone,sta_pos) values ('张三',18,'13417747371','工程师'); insert into staffs(name,age,phone,sta_pos) values ('李四',19,'13417747372','工程师'); insert into staffs(name,age,phone,sta_pos) values ('王五',20,'13417747373','教授'); select * from staffs; |
1、概念
JDBC(Java DataBase Connectity)是Java数据库连接技术的简称,提供连接各种常用数据库的能力;
它提供了操作数据库的接口(规范、标准)。JDBC实现由各数据库厂商提供(驱动程序);
所在包:java.sql
作用:用于连接 + 操作数据库
2、JDBC原理
DriverManager
提供者:sun公司
作用:载入各种不同的JDBC驱动
JDBC 驱动
提供者:数据库厂商
作用:负责连接各种不同的数据库
JDBC<躯壳> VS 驱动程序<灵魂>
3、JDBC的常用对象(API)
1)DriverManager
驱动管理器,管理驱动程序 – 载入各种不同的JDBC驱动程序
2)Connection
连接对象,实现数据库的连接并担任传送数据的任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | package org.zing.util; import java.sql.*; /** * 使用 JDBC 连接 MySQL 数据库 * * @Author zqx * @Date 2023-03-05 */ public class DbUtil { /** * 连接驱动程序 */ private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; /** * 连接URL */ private static final String URL = "jdbc:mysql://127.0.0.1:3306/jdbc_test?useUnicode=true;characterEncoding=utf8;serverTimezone=Asia/Shanghai"; /** * 帐号 */ private static final String USER = "root"; /** * 密码 */ private static final String PASS = "root"; static { /* * 加载驱动程序 */ try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { System.out.println("加载驱动程序失败..."); e.printStackTrace(); } } /** * 获取连接对象 -- Java程序 与 数据库之间的桥梁 * * @return */ public static Connection getConnection() { Connection conn = null; try { conn = DriverManager.getConnection(URL, USER, PASS); } catch (SQLException e) { System.out.println("获取连接对象失败..."); e.printStackTrace(); } return conn; } /** * 关闭相关的 JDBC 对象 * <p> * DriverManager:驱动管理对象,获取连接对象 * <p> * DriverManager.getConnection(URL, USER, PASS); * <p> * ResultSet:结果集对象 用于接收查询数据时,返回的结果 * <p> * Statement:语句对象 用于执行SQL语句(PreparedStatement、CallableStatement) * 增、删、改:executeUpdate() 查询:executeQuery() * <p> * <p> * Connection:连接对象 建立JAVA程序与数据库之间的桥梁 * * @param rst * @param stmt 父类对象可以接收子类对象 - 多态 * @param conn */ public static void close(ResultSet rst, Statement stmt, Connection conn) { if (rst != null) { try { rst.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(); } } } public static void main(String[] args) { System.out.println(DbUtil.getConnection()); } } |
3)Statement
语句对象,由 Connection 产生、负责执行 SQL 语句
3.1)Statement
1 | Statement stmt = conn.createStatement() ; |
3.2)PreparedStatement
效率高、安全性高、方便
1 2 3 4 5 6 7 8 9 10 | //定义操作数据库的SQL语句->数据由?来占位 String sql = "select * from student where id=?"; //预编译SQL语句 PreparedStatement pstmt = conn.prepareStatement(sql); //填充数据,语法如下: // 语句对象.setXxx(占位符索引,数据) -> 从1开始 // Xxx:指是数据类型->Java的数据类型 pstmt.setInt(1, 5); |
3.3)CallableStatement
用于执行存储过程
4、ResultSet
结果集对象,负责保存语句对象执行后所产生的查询结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ResultSet rst = stmt.executeQuery(sql) ; //ResultSet rst = pstmt.executeQuery(); //1.循环迭代各行记录 while(rst.next()) { //2.获取每行中,各字段的数据,语法如下: //方法一:结果集对象.getXxx(字段索引) -> 从1开始,且是根据select后的字段来获取 //方法二:结果集对象.getXxx(字段名称) //注:Xxx指的是字段的数据类型 -> 必须对应Java的数据类型 String stuNO = rst.getString(1) ; String cardID = rst.getString(3) ; //int id = rst.getInt("id") ; //String name = rst.getString("name") ; System.out.println(stuNO+"\t"+cardID); } |
5、ResultSetMetaData
元数据对象,可用于获取有关ResultSet
对象中列的类型和属性的信息的对象。
1 2 3 4 5 6 | // 1.实例化对象:结果集对象.getMetaData() ; ResultSetMetaData metaData = rst.getMetaData() ; // 2.常用方法 // 2.1 结果集中包含列的数量:int getColumnCount(): // 2.2获取指定列的别名,索引从1开始:String getColumnLabel(int column) |
二、DbUtils
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate、MyBatis框架的首选。
commons-dbutils API:
- org.apache.commons.dbutils.QueryRunner:提供对sql语句操作的API
- org.apache.commons.dbutils.ResultSetHandler:用于定义select操作后,怎样封装结果集
- org.apache.commons.dbutils.DbUtils:工具类,定义了关闭资源与事务处理的方法
三、QueryRunner
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
QueryRunner类提供了两个构造方法:
- 默认的构造方法
- 需要一个 javax.sql.DataSource 来作参数的构造方法
常用方法
- public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
- public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException: 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
- public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException : 执行一个不需要置换参数的查询操作。
- public int update(Connection conn, String sql, Object[] params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
- public int update(Connection conn, String sql) throws SQLException:用来执行一个不需要置换参数的更新操作。
四、ResultSetHandler
1.作用
该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。
2.ResultSetHandler接口的实现类
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScalarHandler:它是用于单数据。如果:select count(*) from 表操作。
五、数据库连接池
数据库连接池是一种技术,用于管理应用程序与数据库之间的连接。连接池可以有效地减少数据库连接的开销,提高应用程序的性能和可伸缩性。
六、开源的数据源使用
1、DBCP
略
2、C3P0
作者使用了自己喜欢的一个机器人的代号命名
1)使用方法一
第一、引入jar包:c3p0-0.9.5.jar以及依赖的mchange-commons-java-0.2.9.jar
第二、创建ComboPooledDataSource对象
1 | private static ComboPooledDataSource cpds = new ComboPooledDataSource(); |
第三、设置必须的属性
1 2 3 4 | cpds.setDriverClass("com.microsoft.sqlserver.jdbc.SQLServerDriver"); cpds.setJdbcUrl("jdbc:sqlserver://localhost:1433;database=jdbc"); cpds.setUser("sa"); cpds.setPassword("123456"); |
第四、根据情况,设置可选的属性
第五、获取连接对象
1 | Connection conn = ds.getConnection() ; |
第六、关闭连接对象
1 | ds.close() ; |
2)使用方法二
第一、引入jar包:c3p0-0.9.5.jar以及依赖的mchange-commons-java-0.2.9.jar
第二、配置c3p0-config.xml或c3p0.properties文件(存放在构建路径)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?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://127.0.0.1:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池初始化时创建的连接数 --> <property name="initialPoolSize">10</property> <!-- 连接池保持的最小连接数 --> <property name="minPoolSize">10</property> <!-- 连接池中拥有的最大连接数 --> <property name="maxPoolSize">100</property> <!-- 连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接 --> <property name="maxIdleTime">30</property> <!-- 连接池用完,等待时间,设置0无限等待 --> <property name="checkoutTimeout">30000</property> <!-- 测试空闲连接的间隔时间 --> <property name="idleConnectionTestPeriod">30</property> <!-- 连接池为数据源缓存的PreparedStatement的总数 --> <property name="maxStatements">200</property> </default-config> <!-- 功能同上,配置另一种情况。需要指定一个名称加以区分:other --> <named-config name="other"> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池初始化时创建的连接数 --> <property name="initialPoolSize">10</property> <!-- 连接池保持的最小连接数 --> <property name="minPoolSize">10</property> <!-- 连接池中拥有的最大连接数 --> <property name="maxPoolSize">100</property> <!-- 连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接 --> <property name="maxIdleTime">30</property> <!-- 连接池用完,等待时间,设置0无限等待 --> <property name="checkoutTimeout">30000</property> <!-- 测试空闲连接的间隔时间 --> <property name="idleConnectionTestPeriod">30</property> <!-- 连接池为数据源缓存的PreparedStatement的总数 --> <property name="maxStatements">200</property> </named-config> </c3p0-config> |
第三、创建ComboPooledDataSource对象,并指定配置参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class C3P0Util { /** * 定义C3P0数据源对象 - 使用默认配置创建ComboPooledDataSource对象 */ private static ComboPooledDataSource cpds = new ComboPooledDataSource(); // 使用某配置创建ComboPooledDataSource对象 // private static ComboPooledDataSource cpds = new ComboPooledDataSource("other"); public static DataSource getDataSource() { return cpds; } public static Connection getConnection() { try { return cpds.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } } |
第四、获取连接对象
1 | Connection conn = ds.getConnection() ; |
第五、关闭连接对象
1 | ds.close() ; |
3、Druid
https://github.com/alibaba/druid?tab=readme-ov-file
Druid是什么
Druid是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。
下载
https://mvnrepository.com/
1 2 3 4 5 | <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid-version}</version> </dependency> |
1)使用方法一
第一、引入jar包
第二、创建ComboPooledDataSource对象
1 | private static DruidDataSource dataSource = new DruidDataSource(); |
第三、设置必须的属性
1 2 3 4 | dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("root"); |
第四、根据情况,设置可选的属性
第五、获取连接对象
1 | Connection conn = dataSource.getConnection() ; |
第六、关闭连接对象
1 | conn.close() ; |
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | package org.zing.util; import com.alibaba.druid.pool.DruidDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * @author zqx * @date 2025-02-24 */ public class DruidHelper { /** * 数据源对象 */ private static DruidDataSource dataSource; /** * 获取数据源对象 */ public static DataSource getDataSource() { if (dataSource == null) { initDataSource(); } return dataSource; } /** * 初始化数据源 */ private static void initDataSource() { try { // 实例化数据源对象 dataSource = new DruidDataSource(); // 基础配置 dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 初始化连接数量 dataSource.setInitialSize(10); // 最小连接数 dataSource.setMinIdle(10); // 最大等待时间 dataSource.setMaxWait(6000); // 最大连接数 dataSource.setMaxActive(100); // 连接有效性检测SQL dataSource.setValidationQuery("SELECT 1"); // 空闲时检测连接有效性 dataSource.setTestWhileIdle(true); } catch (Exception e) { throw new RuntimeException("初始化Druid连接池失败", e); } } /** * 从连接池获取连接对象 */ public static Connection getConnection() { try { return getDataSource().getConnection(); } catch (SQLException e) { throw new RuntimeException("获取连接对象失败", e); } } /** * 关闭连接,回收到连接池 * * @param conn 连接对象 */ public static void close(Connection conn) { if (conn != null) { try { conn.close(); } catch (SQLException e) { System.err.println("关闭数据库连接时发生异常"); } } } } |
2)使用方法二
第一、引入jar包
第二、创建配置文件 druid.properties
文件(存放在构建路径)
1 2 3 4 5 6 7 8 9 10 11 12 | driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username=root password=root # 初始化连接数量 initialSize=10 # 最小连接数 minIdle=10 # 最大连接数 maxActive=100 # 最大等待时间 maxWait=6000 |
第三、创建 DataSource 对象,并指定配置参数
1 2 3 | Properties prop = new Properties(); prop.load(DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); |
第四、获取连接对象
1 | Connection conn = dataSource.getConnection() ; |
第五、关闭连接对象
1 | conn.close() ; |
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | package org.zing.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * @author zqx * @date 2025-02-24 */ public class DruidUtil { /** * 数据源对象 */ private static DataSource dataSource; /** * 获取数据源对象 * * @return 数据源对象 */ public static DataSource getDataSource() { if (dataSource == null) { initDataSource(); } return dataSource; } /** * 初始化数据源对象 */ private static void initDataSource() { Properties prop = new Properties(); try { prop.load(DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(prop); } catch (Exception e) { throw new RuntimeException(e); } } /** * 从连接池获取连接对象 * * @return 连接对象 */ public static Connection getConnection() { try { return getDataSource().getConnection(); } catch (SQLException e) { throw new RuntimeException("获取连接对象失败", e); } } /** * 关闭连接,回收到连接池 * * @param conn 连接对象 */ public static void close(Connection conn) { if (conn != null) { try { conn.close(); } catch (SQLException e) { System.err.println("关闭数据库连接时发生异常"); } } } } |
七、DbUtils 增、删、改、查操作
第一:通过C3P0/DBCP获取数据源对象
告知DbUtils操作哪个服务中,哪个端口号的哪个数据库 - 连接池
第二:创建QueryRunner对象
1 | QueryRunner qr = new QueryRunner(C3P0Util.getDataSource()) ; |
第三:定义要操作的SQL语句
1 | String sql = "select id,name,age,phone,pos,add_time as addTime from staffs" ; |
注意:使用别名解决实体对象属性名和数据表字段名不一致的问题
内省
第四:定义SQL语句需要的具体数据
1 | Object[] params = new Object[]{数据1,数据2,...} ; |
第五:执行SQL语句 —— 反射、封装处理
- 查询操作:query(…)
- 更新操作:update(…)
1 2 | List<Staff> list = qr.query(sql, new BeanListHandler<Staff>(Staff.class)); Staff staff = qr.query(sql,new BeanHandler<>(Staff.class),id); |
八、日期时间问题
1、JDBC 类型映射机制
数据库中的时间类型(如 DATETIME
、TIMESTAMP
)通过 JDBC 驱动转换为 Java 类型时,不同版本的驱动处理方式不同:
- MySQL 5.x 驱动:默认将
DATETIME
映射为java.sql.Timestamp
(继承自java.util.Date
) - MySQL 8.x 驱动:支持 JDBC 4.2+,可能直接映射为
java.time.LocalDateTime
当使用 MySQL 8.x 驱动时:
- 数据库
DATETIME
→ JDBC 返回LocalDateTime
BeanProcessor
尝试将LocalDateTime
赋值给java.util.Date
类型属性- 由于无默认转换器,抛出类型不匹配异常
2、解决
1)方案一
使用驱动统一的 java.time
类型
1 2 3 4 5 | public class Staff { // 使用 java.time 类型 private LocalDateTime createTime; // getter/setter } |
2)方案二
第一:自定义 BeanProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package org.zing.dao; import org.apache.commons.dbutils.BeanProcessor; import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; /** * 自定义 BeanProcessor - 实现 LocalDateTime 转换 java.util.Date * * @author zqx * @date 2025-02-25 */ public class CustomBeanProcessor extends BeanProcessor { @Override protected Object processColumn(ResultSet resultSet, int index, Class<?> propType) throws SQLException { // 获取某字段的数据 Object value = super.processColumn(resultSet, index, propType); // 处理 LocalDateTime → Date 的转换 if (value instanceof LocalDateTime localDateTime && propType == Date.class) { return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } // 其它类型的转换... return value; } } |
第二:通过 RowProcessor
注入自定义处理器
1 2 3 4 5 6 7 8 | // 创建自定义 RowProcessor BasicRowProcessor rowProcessor = new BasicRowProcessor(new CustomBeanProcessor()); // 创建 BeanListHandler 时传入自定义处理器 BeanListHandler<Staff> handler = new BeanListHandler<>(Staff.class, rowProcessor); // 执行查询 List<Staff> list = runner.query("select * from staffs", handler); |
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public List<Staff> selectByPage(int limit, int count) { String sql = "select id,name,age,phone,sta_pos as staPos,add_time as addTime,update_time as updateTime from staffs limit ?,?"; List<Staff> list = null; try { // ResultSetHandler VS BeanListHandler // ResultSetHandler 结果集处理器,通过实现此接口来对结果集数据进行处理 // BeanListHandler 是 ResultSetHandler 接口的其中一个实现类,返回实体对象的List集合 Object[] params = {limit, count}; // 创建自定义 RowProcessor BasicRowProcessor rowProcessor = new BasicRowProcessor(new CustomBeanProcessor()); // 创建 BeanListHandler 时传入自定义处理器 BeanListHandler<Staff> handler = new BeanListHandler<>(Staff.class, rowProcessor); // 执行查询 list = qr.query(sql, handler, params); } catch (SQLException e) { throw new RuntimeException(e); } return list; } |