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

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

引言

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

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

1. JDBC核心架构

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

  • DriverManager:管理数据库驱动
  • Connection:数据库连接对象
  • Statement/PreparedStatement:SQL执行接口
  • ResultSet:结果集对象

classDiagram class DriverManager class Connection class Statement class PreparedStatement class ResultSet DriverManager --> Connection Connection --> Statement Connection --> PreparedStatement Statement --> ResultSet PreparedStatement --> ResultSet

2. 完整CRUD实现

2.1 数据库连接管理

java 复制代码
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 查询操作实现

java 复制代码
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 更新操作实现

java 复制代码
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类型转换

classDiagram class SqlSessionFactory class SqlSession class Executor class MappedStatement class TypeHandler SqlSessionFactory --> SqlSession SqlSession --> Executor Executor --> MappedStatement MappedStatement --> TypeHandler

2. MyBatis配置与映射

2.1 配置文件示例

mybatis-config.xml

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 复制代码
<?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创建过程

java 复制代码
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执行:

java 复制代码
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执行流程

java 复制代码
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集成配置

java 复制代码
@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 实体类定义

java 复制代码
@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实现

java 复制代码
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 条件构造器实现

java 复制代码
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 分页插件实现

java 复制代码
@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可能不够优化

四、技术对比与选型建议

特性 JDBC MyBatis MyBatis-Plus
开发效率
性能 中高 中高
灵活性 最高 中高
学习曲线 中高
社区支持 标准 强大 强大
适用场景 需要极致性能/特殊需求 需要灵活SQL控制 快速开发CRUD功能

选型建议

  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框架对比

框架 优点 缺点
Hibernate 全自动ORM,开发效率高 性能较差,复杂查询难优化
JPA 标准规范,可移植性好 灵活性不足
MyBatis SQL可控,性能好 需要写SQL
MyBatis-Plus 开发效率高,功能丰富 复杂SQL支持不够

4. 性能优化建议

  1. 合理使用缓存(一级/二级/分布式)
  2. 批量操作代替循环单条操作
  3. 避免N+1查询问题
  4. 合理设计索引
  5. 使用连接池并正确配置参数

结语

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

相关推荐
Auc241 小时前
OJ判题系统第4期之判题机模块架构——设计思路、实现步骤、代码实现(工厂模式、代理模式的实践)
java·spring cloud·log4j·mybatis·代理模式·工厂模式
冼紫菜14 小时前
【Spring Boot 多模块项目】@MapperScan失效、MapperScannerConfigurer 报错终极解决方案
java·开发语言·mybatis
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧18 小时前
MyBatis快速入门——实操
java·spring boot·spring·intellij-idea·mybatis·intellij idea
菲兹园长1 天前
MyBatis-Plus
java·开发语言·mybatis
计算机学姐1 天前
基于SpringBoot的在线教育管理系统
java·vue.js·spring boot·后端·mysql·spring·mybatis
Kx…………2 天前
Java EE(Spring+Spring MVC+MyBatis)从入门到精通企业级应用开发教程——1初识MyBatis框架
学习·spring·java-ee·mvc·mybatis
码农飞哥2 天前
互联网大厂Java求职面试实战:Spring Boot微服务与数据库优化详解
java·spring boot·微服务·mybatis·数据库优化·性能监控·安全框架
张狂年少2 天前
【十五】Mybatis动态SQL实现原理
java·sql·mybatis
小杜-coding3 天前
黑马点评day04(分布式锁-setnx)
java·spring boot·redis·分布式·spring·java-ee·mybatis