【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 的优缺点分析
优点:
标准 API,所有数据库厂商都提供实现
直接操作底层,性能最高
灵活性最强,可以执行任意 SQL
缺点:
样板代码多,开发效率低
需要手动管理连接和事务
SQL 与 Java 代码耦合度高
需要手动处理异常和资源释放
结果集到对象的映射需要手动实现
二、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 的优缺点分析
优点:
SQL 与 Java 代码分离,易于维护
自动参数映射和结果集映射
支持动态 SQL
提供一级和二级缓存
插件机制支持扩展
缺点:
需要编写 SQL 和映射配置
复杂查询仍需手动编写 SQL
分页功能需要插件支持
代码生成器功能较弱
三、MyBatis-Plus:MyBatis 的增强工具包
1. MyBatis-Plus 核心特性
通用 Mapper:内置常用 CRUD 方法
条件构造器:链式 API 构建查询条件
代码生成器:自动生成 Entity/Mapper/Service 代码
分页插件:内置物理分页支持
性能分析插件:输出 SQL 执行时间
乐观锁插件:支持 @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 的优缺点分析
优点:
极大减少样板代码
强大的条件构造器
内置分页和乐观锁支持
完善的代码生成器
保留 MyBatis 所有特性
缺点:
复杂 SQL 仍需手写 XML
学习成本比原生 MyBatis 高
自动生成的 SQL 可能不够优化
四、技术对比与选型建议
选型建议:
需要极致性能或特殊数据库特性 → JDBC
需要灵活控制 SQL 且项目复杂 → MyBatis
常规业务系统快速开发 → MyBatis-Plus
五、扩展知识点
1. 连接池技术
HikariCP:目前性能最好的连接池
Druid:阿里开源,带监控功能
Tomcat JDBC Pool:Tomcat 内置连接池
2. 分布式事务
XA 协议:传统两阶段提交
TCC 模式:Try-Confirm-Cancel
Saga 模式:长事务解决方案
Seata:阿里开源的分布式事务框架
3. ORM 框架对比
4. 性能优化建议
合理使用缓存(一级/二级/分布式)
批量操作代替循环单条操作
避免 N+1 查询问题
合理设计索引
使用连接池并正确配置参数
结语
通过本文的系统性讲解,我们从最基础的 JDBC 开始,逐步深入到 MyBatis 和 MyBatis-Plus 的核心实现原理。理解这些技术的演进过程和底层机制,有助于我们在实际项目中做出合理的技术选型,并根据业务需求进行适当的定制和优化。无论选择哪种技术,都要在开发效率、维护成本和系统性能之间找到平衡点。
文章转载自:佛祖让我来巡山
不在线第一只蜗牛
还未添加个人签名 2023-06-19 加入
还未添加个人简介









评论