写点什么

【Java 持久层技术演进全解析】从 JDBC 到 MyBatis 再到 MyBatis-Plus

  • 2025-05-15
    福建
  • 本文字数:8930 字

    阅读完需:约 29 分钟

引言


在 Java 企业级应用开发中,数据持久化是核心需求之一。本文将系统性地介绍 Java 持久层技术的演进过程,从最基础的 JDBC 开始,到广泛应用的 MyBatis,再到功能强大的 MyBatis-Plus。通过详细的源码解析和对比分析,帮助开发者深入理解这三种技术的实现原理、优缺点及适用场景。


一、原生 JDBC:数据库操作的基石


1. JDBC 核心架构


JDBC(Java Database Connectivity)是 Java 访问数据库的标准 API,由以下核心组件构成:

  • DriverManager:管理数据库驱动

  • Connection:数据库连接对象

  • Statement/PreparedStatement:SQL 执行接口

  • ResultSet:结果集对象



2. 完整 CRUD 实现


2.1 数据库连接管理


public class JdbcUtil {    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";    private static final String USER = "root";    private static final String PASSWORD = "password";        // 静态代码块加载驱动(JDBC4.0+可省略)    static {        try {            Class.forName("com.mysql.cj.jdbc.Driver");        } catch (ClassNotFoundException e) {            throw new ExceptionInInitializerError("加载数据库驱动失败");        }    }        /**     * 获取数据库连接     * @return Connection对象     * @throws SQLException 如果获取连接失败     */    public static Connection getConnection() throws SQLException {        return DriverManager.getConnection(URL, USER, PASSWORD);    }        /**     * 关闭连接资源     * @param conn 连接对象     * @param stmt Statement对象     * @param rs 结果集对象     */    public static void close(Connection conn, Statement stmt, ResultSet rs) {        try {            if (rs != null) rs.close();            if (stmt != null) stmt.close();            if (conn != null) conn.close();        } catch (SQLException e) {            e.printStackTrace();        }    }}
复制代码


2.2 查询操作实现


public class JdbcQueryExample {    /**     * 查询单个用户     * @param id 用户ID     * @return User对象     */    public User getUserById(int id) {        String sql = "SELECT id, name, age, email FROM users WHERE id = ?";        Connection conn = null;        PreparedStatement pstmt = null;        ResultSet rs = null;        User user = null;                try {            // 1. 获取连接            conn = JdbcUtil.getConnection();            // 2. 创建PreparedStatement(预编译防止SQL注入)            pstmt = conn.prepareStatement(sql);            // 3. 设置参数            pstmt.setInt(1, id);            // 4. 执行查询            rs = pstmt.executeQuery();                        // 5. 处理结果集            if (rs.next()) {                user = new User();                user.setId(rs.getInt("id"));                user.setName(rs.getString("name"));                user.setAge(rs.getInt("age"));                user.setEmail(rs.getString("email"));            }        } catch (SQLException e) {            e.printStackTrace();        } finally {            // 6. 关闭资源            JdbcUtil.close(conn, pstmt, rs);        }        return user;    }        /**     * 查询所有用户(使用try-with-resources简化资源管理)     */    public List<User> getAllUsers() {        String sql = "SELECT id, name, age, email FROM users";        List<User> users = new ArrayList<>();                // try-with-resources自动关闭资源        try (Connection conn = JdbcUtil.getConnection();             Statement stmt = conn.createStatement();             ResultSet rs = stmt.executeQuery(sql)) {                        while (rs.next()) {                User user = new User();                user.setId(rs.getInt("id"));                user.setName(rs.getString("name"));                user.setAge(rs.getInt("age"));                user.setEmail(rs.getString("email"));                users.add(user);            }        } catch (SQLException e) {            e.printStackTrace();        }        return users;    }}
复制代码


2.3 更新操作实现


public class JdbcUpdateExample {    /**     * 更新用户信息     * @param user 用户对象     * @return 影响的行数     */    public int updateUser(User user) {        String sql = "UPDATE users SET name = ?, age = ?, email = ? WHERE id = ?";        int affectedRows = 0;                try (Connection conn = JdbcUtil.getConnection();             PreparedStatement pstmt = conn.prepareStatement(sql)) {                        // 设置参数            pstmt.setString(1, user.getName());            pstmt.setInt(2, user.getAge());            pstmt.setString(3, user.getEmail());            pstmt.setInt(4, user.getId());                        // 执行更新            affectedRows = pstmt.executeUpdate();        } catch (SQLException e) {            e.printStackTrace();        }        return affectedRows;    }        /**     * 批量插入用户     * @param users 用户列表     * @return 每条SQL影响的行数数组     */    public int[] batchInsert(List<User> users) {        String sql = "INSERT INTO users(name, age, email) VALUES (?, ?, ?)";                try (Connection conn = JdbcUtil.getConnection();             PreparedStatement pstmt = conn.prepareStatement(sql)) {                        // 关闭自动提交,开启事务            conn.setAutoCommit(false);                        // 添加批处理            for (User user : users) {                pstmt.setString(1, user.getName());                pstmt.setInt(2, user.getAge());                pstmt.setString(3, user.getEmail());                pstmt.addBatch();            }                        // 执行批处理            int[] results = pstmt.executeBatch();            // 提交事务            conn.commit();            return results;        } catch (SQLException e) {            e.printStackTrace();            return new int[0];        }    }}
复制代码


3. JDBC 的优缺点分析


优点

  1. 标准 API,所有数据库厂商都提供实现

  2. 直接操作底层,性能最高

  3. 灵活性最强,可以执行任意 SQL


缺点

  1. 样板代码多,开发效率低

  2. 需要手动管理连接和事务

  3. SQL 与 Java 代码耦合度高

  4. 需要手动处理异常和资源释放

  5. 结果集到对象的映射需要手动实现


二、MyBatis:SQL 与 Java 的优雅桥梁


1. MyBatis 核心架构


MyBatis 通过以下核心组件简化了 JDBC 操作:

  • SqlSessionFactory:创建 SqlSession 的工厂

  • SqlSession:执行 CRUD 操作的主要接口

  • Executor:SQL 执行器

  • MappedStatement:封装 SQL 语句和映射信息

  • TypeHandler:处理 Java 与 JDBC 类型转换



2. MyBatis 配置与映射


2.1 配置文件示例


mybatis-config.xml


<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <settings>        <setting name="cacheEnabled" value="true"/>        <setting name="lazyLoadingEnabled" value="true"/>    </settings>        <typeAliases>        <typeAlias type="com.example.User" alias="User"/>    </typeAliases>        <environments default="development">        <environment id="development">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>                <property name="url" value="jdbc:mysql://localhost:3306/test"/>                <property name="username" value="root"/>                <property name="password" value="password"/>            </dataSource>        </environment>    </environments>        <mappers>        <mapper resource="mapper/UserMapper.xml"/>    </mappers></configuration>
复制代码


2.2 Mapper XML 示例


UserMapper.xml


<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.UserMapper">    <resultMap id="userResultMap" type="User">        <id property="id" column="id"/>        <result property="name" column="name"/>        <result property="age" column="age"/>        <result property="email" column="email"/>    </resultMap>        <select id="selectUserById" resultMap="userResultMap">        SELECT id, name, age, email FROM users WHERE id = #{id}    </select>        <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">        INSERT INTO users(name, age, email) VALUES(#{name}, #{age}, #{email})    </insert>        <update id="updateUser" parameterType="User">        UPDATE users SET name=#{name}, age=#{age}, email=#{email} WHERE id=#{id}    </update>        <delete id="deleteUser" parameterType="int">        DELETE FROM users WHERE id=#{id}    </delete></mapper>
复制代码


3. MyBatis 核心源码解析


3.1 SqlSession 创建过程


public class MyBatisExample {    public static void main(String[] args) {        // 1. 加载配置文件        String resource = "mybatis-config.xml";        InputStream inputStream = Resources.getResourceAsStream(resource);                // 2. 构建SqlSessionFactory        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);                // 3. 获取SqlSession        try (SqlSession session = sqlSessionFactory.openSession()) {            // 4. 获取Mapper接口代理对象            UserMapper userMapper = session.getMapper(UserMapper.class);                        // 5. 执行CRUD操作            User user = userMapper.selectUserById(1);            System.out.println(user);        } catch (IOException e) {            e.printStackTrace();        }    }}
复制代码


3.2 Mapper 代理实现


MyBatis 通过动态代理将 Mapper 接口方法调用转换为 SQL 执行:


public class MapperProxy<T> implements InvocationHandler, Serializable {    private final SqlSession sqlSession;    private final Class<T> mapperInterface;    private final Map<Method, MapperMethod> methodCache;        @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        // 处理Object方法        if (Object.class.equals(method.getDeclaringClass())) {            return method.invoke(this, args);        }                // 获取缓存的MapperMethod        final MapperMethod mapperMethod = cachedMapperMethod(method);        // 执行SQL        return mapperMethod.execute(sqlSession, args);    }        private MapperMethod cachedMapperMethod(Method method) {        return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));    }}
复制代码


3.3 SQL 执行流程


public class DefaultSqlSession implements SqlSession {    private final Configuration configuration;    private final Executor executor;        @Override    public <E> List<E> selectList(String statement, Object parameter) {        try {            // 1. 获取MappedStatement            MappedStatement ms = configuration.getMappedStatement(statement);            // 2. 委托给Executor执行            return executor.query(ms, wrapCollection(parameter), RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);        } catch (Exception e) {            throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);        }    }}
public class CachingExecutor implements Executor { private final Executor delegate; @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 检查二级缓存 Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }}
复制代码


4. MyBatis 的优缺点分析


优点

  1. SQL 与 Java 代码分离,易于维护

  2. 自动参数映射和结果集映射

  3. 支持动态 SQL

  4. 提供一级和二级缓存

  5. 插件机制支持扩展


缺点

  1. 需要编写 SQL 和映射配置

  2. 复杂查询仍需手动编写 SQL

  3. 分页功能需要插件支持

  4. 代码生成器功能较弱


三、MyBatis-Plus:MyBatis 的增强工具包


1. MyBatis-Plus 核心特性


  1. 通用 Mapper:内置常用 CRUD 方法

  2. 条件构造器:链式 API 构建查询条件

  3. 代码生成器:自动生成 Entity/Mapper/Service 代码

  4. 分页插件:内置物理分页支持

  5. 性能分析插件:输出 SQL 执行时间

  6. 乐观锁插件:支持 @Version 注解


2. MyBatis-Plus 配置与使用


2.1 Spring Boot 集成配置


@Configuration@MapperScan("com.example.mapper")public class MybatisPlusConfig {        @Bean    public MybatisPlusInterceptor mybatisPlusInterceptor() {        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();        // 添加分页插件        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));        // 添加乐观锁插件        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());        return interceptor;    }        @Bean    public GlobalConfig globalConfig() {        GlobalConfig globalConfig = new GlobalConfig();        globalConfig.setMetaObjectHandler(new MetaHandler());        return globalConfig;    }}
复制代码


2.2 实体类定义


@Data@TableName("users")public class User {    @TableId(type = IdType.AUTO)    private Long id;        private String name;    private Integer age;    private String email;        @Version    private Integer version;        @TableField(fill = FieldFill.INSERT)    private Date createTime;        @TableField(fill = FieldFill.INSERT_UPDATE)    private Date updateTime;}
复制代码


3. MyBatis-Plus 核心源码解析


3.1 通用 Mapper 实现


public interface BaseMapper<T> extends Mapper<T> {    int insert(T entity);    int deleteById(Serializable id);    int updateById(@Param(Constants.ENTITY) T entity);    T selectById(Serializable id);    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);    // 其他方法...}
public class MybatisMapperProxy<T> implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 处理默认方法 if (method.isDefault()) { return invokeDefaultMethod(proxy, method, args); } // 处理Wrapper条件 if (args != null && args.length > 0 && args[0] instanceof Wrapper) { processWrapper((Wrapper<?>) args[0]); } // 转换为MyBatis的MapperMethod执行 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } private void processWrapper(Wrapper<?> wrapper) { if (wrapper instanceof AbstractWrapper) { ((AbstractWrapper<?, ?, ?>) wrapper).checkEntityClass(); } }}
复制代码


3.2 条件构造器实现


public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>>     implements Wrapper<T>, Compare<Children, R> {        protected final List<SqlSegment> sqlSegments = new ArrayList<>();    protected Entity<T> entity;        public Children eq(R column, Object val) {        return addCondition(column, SqlKeyword.EQ, val);    }        protected Children addCondition(R column, SqlKeyword keyword, Object val) {        String columnName = columnToString(column);        sqlSegments.add(new SimpleSqlSegment(columnName + keyword.getSqlSegment() + "?"));        paramNameValuePairs.put(columnName, val);        return typedThis;    }        public String getSqlSegment() {        mergeExpression();        return sqlSegments.stream()            .map(SqlSegment::getSqlSegment)            .collect(Collectors.joining(" "));    }}
复制代码


3.3 分页插件实现


@Intercepts({    @Signature(type = Executor.class, method = "query",               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),    @Signature(type = Executor.class, method = "query",               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})public class PaginationInnerInterceptor implements InnerInterceptor {        @Override    public void beforeQuery(Executor executor, MappedStatement ms,                           Object parameter, RowBounds rowBounds,                           ResultHandler resultHandler, BoundSql boundSql) {        if (parameter instanceof Map && ((Map<?, ?>) parameter).containsKey(IPage.class.getName())) {            // 获取分页参数            IPage<?> page = (IPage<?>) ((Map<?, ?>) parameter).get(IPage.class.getName());                        // 执行COUNT查询            String countSql = generateCountSql(boundSql.getSql());            Long total = executeCount(executor, ms, parameter, boundSql, countSql);            page.setTotal(total);                        // 生成分页SQL            String pageSql = dialect.buildPaginationSql(boundSql.getSql(), page, buildCountKey(ms.getId()));                        // 修改BoundSql            resetSql(boundSql, pageSql);        }    }}
复制代码


4. MyBatis-Plus 的优缺点分析


优点

  1. 极大减少样板代码

  2. 强大的条件构造器

  3. 内置分页和乐观锁支持

  4. 完善的代码生成器

  5. 保留 MyBatis 所有特性


缺点

  1. 复杂 SQL 仍需手写 XML

  2. 学习成本比原生 MyBatis 高

  3. 自动生成的 SQL 可能不够优化


四、技术对比与选型建议



选型建议

  1. 需要极致性能或特殊数据库特性 → JDBC

  2. 需要灵活控制 SQL 且项目复杂 → MyBatis

  3. 常规业务系统快速开发 → MyBatis-Plus


五、扩展知识点


1. 连接池技术

  • HikariCP:目前性能最好的连接池

  • Druid:阿里开源,带监控功能

  • Tomcat JDBC Pool:Tomcat 内置连接池


2. 分布式事务

  • XA 协议:传统两阶段提交

  • TCC 模式:Try-Confirm-Cancel

  • Saga 模式:长事务解决方案

  • Seata:阿里开源的分布式事务框架


3. ORM 框架对比



4. 性能优化建议

  1. 合理使用缓存(一级/二级/分布式)

  2. 批量操作代替循环单条操作

  3. 避免 N+1 查询问题

  4. 合理设计索引

  5. 使用连接池并正确配置参数


结语


通过本文的系统性讲解,我们从最基础的 JDBC 开始,逐步深入到 MyBatis 和 MyBatis-Plus 的核心实现原理。理解这些技术的演进过程和底层机制,有助于我们在实际项目中做出合理的技术选型,并根据业务需求进行适当的定制和优化。无论选择哪种技术,都要在开发效率、维护成本和系统性能之间找到平衡点。


文章转载自:佛祖让我来巡山

原文链接:https://www.cnblogs.com/sun-10387834/p/18873873

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
【Java持久层技术演进全解析】从JDBC到MyBatis再到MyBatis-Plus_Java_不在线第一只蜗牛_InfoQ写作社区