从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 --> ResultSet2. 完整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类型转换
classDiagram class SqlSessionFactory class SqlSession class Executor class MappedStatement class TypeHandler SqlSessionFactory --> SqlSession SqlSession --> Executor Executor --> MappedStatement MappedStatement --> TypeHandler2. 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可能不够优化
四、技术对比与选型建议
特性JDBCMyBatisMyBatis-Plus开发效率低中高性能高中高中高灵活性最高高中高学习曲线低中中高社区支持标准强大强大适用场景需要极致性能/特殊需求需要灵活SQL控制快速开发CRUD功能选型建议:
- 需要极致性能或特殊数据库特性 → JDBC
- 需要灵活控制SQL且项目复杂 → MyBatis
- 常规业务系统快速开发 → MyBatis-Plus
五、扩展知识点
1. 连接池技术
- HikariCP:目前性能最好的连接池
- Druid:阿里开源,带监控功能
- Tomcat JDBC Pool:Tomcat内置连接池
2. 分布式事务
- XA协议:传统两阶段提交
- TCC模式:Try-Confirm-Cancel
- Saga模式:长事务解决方案
- Seata:阿里开源的分布式事务框架
3. ORM框架对比
框架优点缺点Hibernate全自动ORM,开发效率高性能较差,复杂查询难优化JPA标准规范,可移植性好灵活性不足MyBatisSQL可控,性能好需要写SQLMyBatis-Plus开发效率高,功能丰富复杂SQL支持不够4. 性能优化建议
- 合理使用缓存(一级/二级/分布式)
- 批量操作代替循环单条操作
- 避免N+1查询问题
- 合理设计索引
- 使用连接池并正确配置参数
结语
通过本文的系统性讲解,我们从最基础的JDBC开始,逐步深入到MyBatis和MyBatis-Plus的核心实现原理。理解这些技术的演进过程和底层机制,有助于我们在实际项目中做出合理的技术选型,并根据业务需求进行适当的定制和优化。无论选择哪种技术,都要在开发效率、维护成本和系统性能之间找到平衡点。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |