一、前置准备(统一基础环境)
1. MySQL 表结构(不变)
sql
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`age` INT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2. 通用依赖(新增 C3P0 依赖)
xml
<!-- MySQL驱动(三者共用) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- DbUtils依赖 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- MyBatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- C3P0数据源核心依赖 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<!-- 日志(MyBatis调试用) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
3. 通用实体类(不变)
java
运行
public class User {
private Integer id;
private String username;
private Integer age;
// 无参构造(MyBatis/DbUtils必需)
public User() {}
// 有参构造
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
// getter/setter
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
// toString
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
'}';
}
}
4. C3P0 核心 XML 配置文件(c3p0-config.xml)
放置在resources根目录下(C3P0 默认读取该路径的配置文件):
xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 默认配置(DbUtils/MyBatis共用) -->
<default-config>
<!-- 数据库连接基本信息 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池核心配置 -->
<property name="initialPoolSize">5</property> <!-- 初始连接数 -->
<property name="maxPoolSize">20</property> <!-- 最大连接数 -->
<property name="minPoolSize">5</property> <!-- 最小连接数 -->
<property name="maxIdleTime">300</property> <!-- 连接最大空闲时间(秒) -->
<property name="acquireIncrement">2</property> <!-- 连接不足时的增量 -->
<property name="checkoutTimeout">3000</property> <!-- 获取连接超时时间(毫秒) -->
</default-config>
<!-- 自定义配置(可选,可针对不同环境配置) -->
<named-config name="custom-c3p0-config">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="initialPoolSize">10</property>
<property name="maxPoolSize">30</property>
</named-config>
</c3p0-config>
二、Demo 改造(DbUtils + MyBatis 适配 C3P0 XML 配置)
Demo1:原生 JDBC 实现(不变,作为对比基准)
java
运行
import java.sql.*;
public class JdbcDemo {
// 数据库连接配置
private static final String URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PWD = "123456";
// 根据ID查询用户
public static User getUserById(Integer id) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
User user = null;
try {
// 1. 获取连接
conn = DriverManager.getConnection(URL, USER, PWD);
// 2. 创建预编译语句
String sql = "SELECT id, username, age FROM user WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
// 3. 执行查询
rs = pstmt.executeQuery();
// 4. 手动映射结果集
if (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setAge(rs.getInt("age"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5. 手动关闭资源(先开后关)
try { if (rs != null) rs.close(); } catch (SQLException e) {}
try { if (pstmt != null) pstmt.close(); } catch (SQLException e) {}
try { if (conn != null) conn.close(); } catch (SQLException e) {}
}
return user;
}
// 插入用户(手动事务)
public static boolean insertUser(User user) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
// 1. 获取连接
conn = DriverManager.getConnection(URL, USER, PWD);
// 2. 关闭自动提交(开启事务)
conn.setAutoCommit(false);
// 3. 创建预编译语句
String sql = "INSERT INTO user(username, age) VALUES (?, ?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, user.getUsername());
pstmt.setInt(2, user.getAge());
// 4. 执行插入
int rows = pstmt.executeUpdate();
// 5. 提交事务
conn.commit();
return rows > 0;
} catch (SQLException e) {
// 6. 异常回滚
try { if (conn != null) conn.rollback(); } catch (SQLException ex) {}
e.printStackTrace();
return false;
} finally {
// 7. 关闭资源
try { if (pstmt != null) pstmt.close(); } catch (SQLException e) {}
try { if (conn != null) conn.close(); } catch (SQLException e) {}
}
}
public static void main(String[] args) {
// 查询测试
User user = getUserById(1);
System.out.println("JDBC查询结果:" + user);
// 插入测试
User newUser = new User("test_jdbc", 25);
boolean success = insertUser(newUser);
System.out.println("JDBC插入结果:" + success);
}
}
Demo2:DbUtils 实现(C3P0 XML 数据源版)
java
运行
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DbUtilsDemo {
// 读取C3P0 XML配置(默认配置)
private static DataSource getC3p0DataSource() {
// ComboPooledDataSource默认读取resources/c3p0-config.xml的default-config
// 若要读取自定义配置:new ComboPooledDataSource("custom-c3p0-config")
return new ComboPooledDataSource();
}
// 根据ID查询用户(自动资源关闭 + 自动结果映射)
public static User getUserById(Integer id) throws SQLException {
// 1. 创建QueryRunner(传入C3P0数据源)
QueryRunner qr = new QueryRunner(getC3p0DataSource());
// 2. 执行查询(BeanHandler自动映射为User)
String sql = "SELECT id, username, age FROM user WHERE id = ?";
return qr.query(sql, new BeanHandler<>(User.class), id);
}
// 插入用户(手动事务)
public static boolean insertUser(User user) {
Connection conn = null;
try {
// 1. 从C3P0数据源手动获取连接(事务需要)
conn = getC3p0DataSource().getConnection();
conn.setAutoCommit(false);
// 2. 创建无参QueryRunner(手动控制连接)
QueryRunner qr = new QueryRunner();
String sql = "INSERT INTO user(username, age) VALUES (?, ?)";
// 3. 执行插入
int rows = qr.update(conn, sql, user.getUsername(), user.getAge());
// 4. 提交事务
conn.commit();
return rows > 0;
} catch (SQLException e) {
// 5. 回滚 + 安静关闭
DbUtils.rollbackAndClose(conn);
e.printStackTrace();
return false;
} finally {
// 6. 安静关闭连接
DbUtils.closeQuietly(conn);
}
}
public static void main(String[] args) throws SQLException {
// 查询测试
User user = getUserById(1);
System.out.println("DbUtils查询结果:" + user);
// 插入测试
User newUser = new User("test_dbutils_c3p0", 26);
boolean success = insertUser(newUser);
System.out.println("DbUtils插入结果:" + success);
}
}
Demo3:MyBatis 实现(C3P0 XML 数据源版)
核心改造:MyBatis 配置文件中替换为 C3P0 数据源,读取 XML 配置
步骤 1:MyBatis 核心配置文件(mybatis-config.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境配置:整合C3P0数据源 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/> <!-- 事务管理复用JDBC -->
<!-- 替换为C3P0数据源(type="POOLED"改为自定义C3P0实现) -->
<dataSource type="com.example.mybatis.C3P0DataSourceFactory">
<!-- 无需硬编码配置,C3P0内部读取XML -->
<property name="configName" value="default-config"/> <!-- 指定C3P0配置名称 -->
</dataSource>
</environment>
</environments>
<!-- 映射器配置 -->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
步骤 2:MyBatis 整合 C3P0 的数据源工厂类
MyBatis 的DataSourceFactory接口实现,用于加载 C3P0 XML 配置:
java
运行
import org.apache.ibatis.datasource.DataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.util.Properties;
public class C3P0DataSourceFactory implements DataSourceFactory {
private Properties props;
private ComboPooledDataSource dataSource;
public C3P0DataSourceFactory() {
this.dataSource = new ComboPooledDataSource();
}
@Override
public void setProperties(Properties props) {
this.props = props;
// 读取MyBatis配置中指定的C3P0配置名称(default-config/custom-c3p0-config)
String configName = props.getProperty("configName", "default-config");
// 重新初始化C3P0数据源,读取指定名称的XML配置
this.dataSource = new ComboPooledDataSource(configName);
}
@Override
public DataSource getDataSource() {
return this.dataSource;
}
}
步骤 3:Mapper 映射文件(UserMapper.xml,不变)
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 命名空间对应Mapper接口 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 根据ID查询用户(自动映射) -->
<select id="getUserById" parameterType="java.lang.Integer" resultType="com.example.entity.User">
SELECT id, username, age FROM user WHERE id = #{id}
</select>
<!-- 插入用户 -->
<insert id="insertUser" parameterType="com.example.entity.User">
INSERT INTO user(username, age) VALUES (#{username}, #{age})
</insert>
</mapper>
步骤 4:Mapper 接口(不变)
java
运行
package com.example.mapper;
import com.example.entity.User;
public interface UserMapper {
// 根据ID查询用户
User getUserById(Integer id);
// 插入用户
int insertUser(User user);
}
步骤 5:MyBatis 核心实现类
java
运行
import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisDemo {
// 构建SqlSessionFactory(全局唯一)
private static SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
// 根据ID查询用户
public static User getUserById(Integer id) throws IOException {
// 1. 获取SqlSession(自动管理C3P0连接)
try (SqlSession session = getSqlSessionFactory().openSession(true)) { // true=自动提交
// 2. 获取Mapper代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
// 3. 调用方法(MyBatis自动执行SQL+映射结果)
return mapper.getUserById(id);
}
}
// 插入用户(手动事务)
public static boolean insertUser(User user) throws IOException {
// 1. 打开SqlSession(关闭自动提交)
try (SqlSession session = getSqlSessionFactory().openSession(false)) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 2. 执行插入
int rows = mapper.insertUser(user);
// 3. 提交事务
session.commit();
return rows > 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static void main(String[] args) throws IOException {
// 查询测试
User user = getUserById(1);
System.out.println("MyBatis查询结果:" + user);
// 插入测试
User newUser = new User("test_mybatis_c3p0", 27);
boolean success = insertUser(newUser);
System.out.println("MyBatis插入结果:" + success);
}
}
三、深度问答解析
问题 1:DbUtils 整合 C3P0 XML 配置的核心逻辑是什么?和硬编码数据源有什么区别?
引导思考:C3P0 如何读取 XML 配置?XML 配置的优势是什么?核心解答:
- 核心逻辑:
- C3P0 的
ComboPooledDataSource默认会扫描resources根目录下的c3p0-config.xml; - 无参构造
new ComboPooledDataSource()读取<default-config>,带参构造new ComboPooledDataSource("custom-c3p0-config")读取指定<named-config>; - DbUtils 的
QueryRunner只需传入 C3P0 数据源实例,即可自动从连接池获取 / 归还连接,无需手动管理连接创建。
- C3P0 的
- XML 配置 vs 硬编码的优势:
- 解耦:数据库配置与代码分离,修改配置无需重新编译代码;
- 多环境适配:可配置
<named-config>分别对应开发 / 测试 / 生产环境,仅需修改构造参数即可切换; - 连接池参数集中管理:连接池的初始连接数、最大连接数等参数统一配置,便于调优。
问题 2:MyBatis 整合 C3P0 为什么需要自定义DataSourceFactory?核心作用是什么?
引导思考:MyBatis 默认的POOLED数据源是内置实现,如何适配第三方数据源(如 C3P0)?核心解答:
- 底层原因:MyBatis 的
<dataSource>标签默认只支持UNPOOLED/POOLED/JNDI三种类型,无法直接识别 C3P0,因此需要实现DataSourceFactory接口适配第三方数据源; - 自定义
C3P0DataSourceFactory的核心作用:- 实现
setProperties()方法,接收 MyBatis 配置中传递的参数(如 C3P0 配置名称); - 初始化 C3P0 数据源实例,读取 XML 配置文件;
- 实现
getDataSource()方法,向 MyBatis 返回 C3P0 数据源实例,让 MyBatis 通过 C3P0 连接池管理连接。
- 实现
问题 3:使用 C3P0 连接池后,DbUtils/MyBatis 的连接管理和原生 JDBC 有什么本质差异?
引导思考:原生 JDBC 每次DriverManager.getConnection()都是新建物理连接,C3P0 连接池的 “连接复用” 如何实现?核心解答:
| 维度 | 原生 JDBC | DbUtils+C3P0/MyBatis+C3P0 |
|---|---|---|
| 连接创建方式 | 每次调用getConnection()新建 TCP 物理连接 | 从 C3P0 连接池获取空闲连接(复用),无需新建物理连接 |
| 连接销毁方式 | 调用close()关闭物理连接(TCP 断开) | 调用close()将连接归还连接池(物理连接不关闭) |
| 性能 | 高开销(TCP 连接 / 断开耗时) | 低开销(连接复用,仅管理连接状态) |
| 资源管控 | 无限制创建连接,易导致数据库连接数超限 | 连接池限制最大连接数,避免资源耗尽 |
问题 4:C3P0 的 XML 配置中maxIdleTime/checkoutTimeout等参数的核心作用是什么?对业务有什么影响?
引导思考:连接池参数配置不当会导致什么问题(如连接泄漏、获取连接超时)?
核心解答:
maxIdleTime(连接最大空闲时间):空闲连接超过该时间会被 C3P0 自动关闭,释放数据库资源,避免连接长期空闲占用资源;checkoutTimeout(获取连接超时时间):当连接池无空闲连接时,等待该时间仍未获取到连接则抛出异常,避免业务线程无限等待;maxPoolSize(最大连接数):限制连接池的最大物理连接数,需根据数据库的max_connections参数配置(如数据库最大连接数为 100,则 C3P0 的maxPoolSize建议配置为 80-90),避免超出数据库承载能力。
问题 5:DbUtils 和 MyBatis 使用 C3P0 的核心注意事项是什么?
核心解答:
- DbUtils 注意事项:
- 事务场景下,手动获取的连接需确保
commit()/rollback()后调用close()归还连接池,避免连接泄漏; - 避免长时间占用连接(如执行业务逻辑时持有连接),否则会导致连接池无空闲连接,其他请求阻塞。
- 事务场景下,手动获取的连接需确保
- MyBatis 注意事项:
SqlSession关闭时会自动将连接归还 C3P0,需确保SqlSession在try-with-resources中自动关闭,或手动调用close();- 避免
SqlSession长时间存活(如全局单例SqlSession),否则会导致连接长期被占用,连接池耗尽。
四、总结:C3P0 XML 配置核心价值
- 解耦配置与代码:数据库连接信息、连接池参数通过 XML 配置,无需硬编码,便于维护和多环境切换;
- 连接复用提升性能:C3P0 连接池复用物理连接,避免原生 JDBC 频繁创建 / 关闭连接的性能损耗;
- 资源管控更安全:连接池限制最大连接数、空闲时间等参数,避免数据库连接数超限导致的服务不可用;
- 框架适配性强:DbUtils 可直接复用 C3P0 数据源,MyBatis 通过自定义
DataSourceFactory适配后,也能无缝使用 C3P0 的连接池能力。
从原生 JDBC → DbUtils+C3P0 → MyBatis+C3P0,核心演进逻辑是:从 “手动管理所有细节” 到 “框架封装细节 + 连接池优化性能 + 配置解耦提升可维护性”,这也是企业级开发中数据库操作的主流实践方式。

3350

被折叠的 条评论
为什么被折叠?



