原生 JDBC + DbUtils + MyBatis 同场景 Demo(C3P0 数据源 XML 配置版)

一、前置准备(统一基础环境)

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 配置的优势是什么?核心解答

  1. 核心逻辑:
    • C3P0 的ComboPooledDataSource默认会扫描resources根目录下的c3p0-config.xml
    • 无参构造new ComboPooledDataSource()读取<default-config>,带参构造new ComboPooledDataSource("custom-c3p0-config")读取指定<named-config>
    • DbUtils 的QueryRunner只需传入 C3P0 数据源实例,即可自动从连接池获取 / 归还连接,无需手动管理连接创建。
  2. XML 配置 vs 硬编码的优势:
    • 解耦:数据库配置与代码分离,修改配置无需重新编译代码;
    • 多环境适配:可配置<named-config>分别对应开发 / 测试 / 生产环境,仅需修改构造参数即可切换;
    • 连接池参数集中管理:连接池的初始连接数、最大连接数等参数统一配置,便于调优。

问题 2:MyBatis 整合 C3P0 为什么需要自定义DataSourceFactory?核心作用是什么?

引导思考:MyBatis 默认的POOLED数据源是内置实现,如何适配第三方数据源(如 C3P0)?核心解答

  1. 底层原因:MyBatis 的<dataSource>标签默认只支持UNPOOLED/POOLED/JNDI三种类型,无法直接识别 C3P0,因此需要实现DataSourceFactory接口适配第三方数据源;
  2. 自定义C3P0DataSourceFactory的核心作用:
    • 实现setProperties()方法,接收 MyBatis 配置中传递的参数(如 C3P0 配置名称);
    • 初始化 C3P0 数据源实例,读取 XML 配置文件;
    • 实现getDataSource()方法,向 MyBatis 返回 C3P0 数据源实例,让 MyBatis 通过 C3P0 连接池管理连接。

问题 3:使用 C3P0 连接池后,DbUtils/MyBatis 的连接管理和原生 JDBC 有什么本质差异?

引导思考:原生 JDBC 每次DriverManager.getConnection()都是新建物理连接,C3P0 连接池的 “连接复用” 如何实现?核心解答

维度原生 JDBCDbUtils+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 的核心注意事项是什么?

核心解答

  1. DbUtils 注意事项:
    • 事务场景下,手动获取的连接需确保commit()/rollback()后调用close()归还连接池,避免连接泄漏;
    • 避免长时间占用连接(如执行业务逻辑时持有连接),否则会导致连接池无空闲连接,其他请求阻塞。
  2. MyBatis 注意事项:
    • SqlSession关闭时会自动将连接归还 C3P0,需确保SqlSessiontry-with-resources中自动关闭,或手动调用close()
    • 避免SqlSession长时间存活(如全局单例SqlSession),否则会导致连接长期被占用,连接池耗尽。

四、总结:C3P0 XML 配置核心价值

  1. 解耦配置与代码:数据库连接信息、连接池参数通过 XML 配置,无需硬编码,便于维护和多环境切换;
  2. 连接复用提升性能:C3P0 连接池复用物理连接,避免原生 JDBC 频繁创建 / 关闭连接的性能损耗;
  3. 资源管控更安全:连接池限制最大连接数、空闲时间等参数,避免数据库连接数超限导致的服务不可用;
  4. 框架适配性强:DbUtils 可直接复用 C3P0 数据源,MyBatis 通过自定义DataSourceFactory适配后,也能无缝使用 C3P0 的连接池能力。

从原生 JDBC → DbUtils+C3P0 → MyBatis+C3P0,核心演进逻辑是:从 “手动管理所有细节” 到 “框架封装细节 + 连接池优化性能 + 配置解耦提升可维护性”,这也是企业级开发中数据库操作的主流实践方式。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值