前言 在Java后端开发中,MyBatis生态系统提供了多种数据访问解决方案:原生MyBatis、MyBatis Plus、MyBatis Plus Join,以及直接编写SQL语句。每种方案在开发效率、运行性能、维护成本等方面都有不同的特点。本文将深入分析这些方案的效率差异,并提供针对性的优化策略。
一、框架概述与特点分析 (一)MyBatis原生框架 MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。
特点分析 灵活性高 :完全控制SQL语句学习成本低 :接近原生SQL开发性能可控 :手动优化SQL性能代码量大 :需要编写大量XML配置适用场景 1 2 3 4 5 6 7 8 9 @Mapper public interface UserMapper { @Select("SELECT u.*, p.profile_name FROM users u " + "LEFT JOIN profiles p ON u.id = p.user_id " + "WHERE u.status = #{status} AND u.create_time > #{startTime}") List<UserVO> findUsersWithProfile (@Param("status") Integer status, @Param("startTime") Date startTime) ;}
(二)MyBatis Plus框架 MyBatis Plus是MyBatis的增强工具,在MyBatis的基础上只做增强不做改变。
特点分析 开发效率高 :内置CRUD操作代码生成器 :自动生成基础代码条件构造器 :链式调用构建查询分页插件 :内置分页功能核心功能示例 1 2 3 4 5 6 7 8 9 10 11 @Service public class UserService extends ServiceImpl <UserMapper, User> { public List<User> findActiveUsers () { return lambdaQuery() .eq(User::getStatus, 1 ) .gt(User::getCreateTime, LocalDateTime.now().minusDays(30 )) .list(); } }
(三)MyBatis Plus Join框架 MyBatis Plus Join是基于MyBatis Plus的连表查询增强框架。
特点分析 连表查询简化 :类型安全的连表操作性能优化 :智能SQL生成代码简洁 :减少手写SQL学习成本 :需要掌握新的API连表查询示例 1 2 3 4 5 6 7 8 9 public List<UserVO> findUsersWithOrders () { return mpjLambdaWrapper() .selectAll(User.class) .select(Order::getOrderNo, Order::getAmount) .leftJoin(Order.class, Order::getUserId, User::getId) .eq(User::getStatus, 1 ) .list(UserVO.class); }
(四)原生SQL方案 直接使用JDBC或JdbcTemplate编写SQL语句。
特点分析 性能最优 :无ORM框架开销控制精确 :完全控制SQL执行开发复杂 :需要处理结果集映射维护困难 :SQL分散在代码中二、性能效率对比分析 (一)开发效率对比 框架 简单CRUD 复杂查询 连表查询 代码量 学习成本 MyBatis原生 ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 大 低 MyBatis Plus ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐ 小 中 MP Join ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 小 中 原生SQL ⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 大 低
(二)运行性能对比 1. SQL生成效率 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void performanceTest () { long start = System.currentTimeMillis(); for (int i = 0 ; i < 10000 ; i++) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.eq("status" , 1 ).gt("create_time" , new Date ()); } long mpTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); for (int i = 0 ; i < 10000 ; i++) { String sql = "SELECT * FROM users WHERE status = ? AND create_time > ?" ; } long nativeTime = System.currentTimeMillis() - start; }
2. 内存占用分析 MyBatis原生 :内存占用最小,约50-100MBMyBatis Plus :增加约20-30MB内存开销MP Join :额外增加约10-20MB内存开销原生SQL :内存占用最小,约30-50MB3. 执行效率对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void batchInsertTest () { List<User> users = generateTestUsers(10000 ); long start = System.currentTimeMillis(); userService.saveBatch(users, 1000 ); long mpTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); jdbcTemplate.batchUpdate(sql, batchArgs); long nativeTime = System.currentTimeMillis() - start; }
三、使用场景分析 (一)MyBatis原生适用场景 复杂业务查询 :需要精确控制SQL的场景性能敏感应用 :对性能要求极高的系统遗留系统改造 :已有大量SQL的系统数据库特性依赖 :需要使用特定数据库功能1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Select(""" SELECT DATE_FORMAT(create_time, '%Y-%m') as month, COUNT(*) as total_orders, SUM(amount) as total_amount, AVG(amount) as avg_amount FROM orders WHERE create_time >= #{startDate} AND create_time <= #{endDate} AND status IN <foreach collection="statusList" item="status" open="(" close=")" separator=","> #{status} </foreach> GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month DESC """) List<OrderStatistics> getOrderStatistics (@Param("startDate") Date startDate, @Param("endDate") Date endDate, @Param("statusList") List<Integer> statusList) ;
(二)MyBatis Plus适用场景 快速开发 :需要快速构建CRUD功能中小型项目 :业务逻辑相对简单标准化操作 :大部分操作都是标准CRUD团队协作 :需要统一开发规范1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Service public class ProductService extends ServiceImpl <ProductMapper, Product> { public IPage<Product> getProductPage (ProductQuery query) { Page<Product> page = new Page <>(query.getCurrent(), query.getSize()); LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper <>(); wrapper.like(StringUtils.isNotBlank(query.getName()), Product::getName, query.getName()) .eq(Objects.nonNull(query.getCategoryId()), Product::getCategoryId, query.getCategoryId()) .between(Objects.nonNull(query.getStartPrice()) && Objects.nonNull(query.getEndPrice()), Product::getPrice, query.getStartPrice(), query.getEndPrice()) .orderByDesc(Product::getCreateTime); return page(page, wrapper); } }
(三)MyBatis Plus Join适用场景 多表关联查询 :需要频繁进行连表操作类型安全要求 :希望编译期检查字段名中等复杂度查询 :比简单CRUD复杂,但不需要手写SQL快速原型开发 :需要快速实现连表功能1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public List<OrderDetailVO> getOrderDetails (OrderQuery query) { return mpjLambdaWrapper() .selectAll(Order.class) .select(User::getUsername, User::getEmail) .select(Product::getName, Product::getPrice) .leftJoin(User.class, User::getId, Order::getUserId) .leftJoin(OrderItem.class, OrderItem::getOrderId, Order::getId) .leftJoin(Product.class, Product::getId, OrderItem::getProductId) .eq(StringUtils.isNotBlank(query.getOrderNo()), Order::getOrderNo, query.getOrderNo()) .between(Objects.nonNull(query.getStartDate()) && Objects.nonNull(query.getEndDate()), Order::getCreateTime, query.getStartDate(), query.getEndDate()) .orderByDesc(Order::getCreateTime) .list(OrderDetailVO.class); }
(四)原生SQL适用场景 极致性能要求 :对性能要求极高的核心业务复杂数据处理 :需要使用数据库特有功能大数据量操作 :批量数据处理和ETL操作底层框架开发 :开发基础框架和工具1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Repository public class HighPerformanceUserDao { @Autowired private JdbcTemplate jdbcTemplate; public void batchInsertUsers (List<User> users) { String sql = "INSERT INTO users (username, email, status, create_time) VALUES (?, ?, ?, ?)" ; jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter () { @Override public void setValues (PreparedStatement ps, int i) throws SQLException { User user = users.get(i); ps.setString(1 , user.getUsername()); ps.setString(2 , user.getEmail()); ps.setInt(3 , user.getStatus()); ps.setTimestamp(4 , Timestamp.valueOf(user.getCreateTime())); } @Override public int getBatchSize () { return users.size(); } }); } }
四、各框架优化策略详解 (一)MyBatis原生优化策略 1. SQL优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <resultMap id ="UserResultMap" type ="com.example.entity.User" > <id column ="id" property ="id" /> <result column ="username" property ="username" /> <result column ="email" property ="email" /> <result column ="create_time" property ="createTime" /> </resultMap > <select id ="findUsersByCondition" resultMap ="UserResultMap" > SELECT id, username, email, create_time FROM users <where > <if test ="status != null" > AND status = #{status} </if > <if test ="startTime != null" > AND create_time >= #{startTime} </if > <if test ="endTime != null" > AND create_time <= #{endTime} </if > </where > ORDER BY create_time DESC LIMIT #{offset}, #{limit} </select >
2. 缓存配置优化 1 2 3 4 5 6 7 <cache eviction ="LRU" flushInterval ="60000" size ="512" readOnly ="true" /> <select id ="findUserById" resultMap ="UserResultMap" useCache ="true" > SELECT * FROM users WHERE id = #{id} </select >
3. 批量操作优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Insert(""" <script> INSERT INTO users (username, email, status, create_time) VALUES <foreach collection="users" item="user" separator=","> (#{user.username}, #{user.email}, #{user.status}, #{user.createTime}) </foreach> </script> """) void batchInsertUsers (@Param("users") List<User> users) ;public void saveBatchUsers (List<User> users) { int batchSize = 1000 ; for (int i = 0 ; i < users.size(); i += batchSize) { int end = Math.min(i + batchSize, users.size()); List<User> batch = users.subList(i, end); userMapper.batchInsertUsers(batch); } }
4. 连接池优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 spring: datasource: type: com.zaxxer.hikari.HikariDataSource hikari: minimum-idle: 10 maximum-pool-size: 50 idle-timeout: 300000 max-lifetime: 1800000 connection-timeout: 30000 validation-timeout: 5000 leak-detection-threshold: 60000 cachePrepStmts: true prepStmtCacheSize: 250 prepStmtCacheSqlLimit: 2048
(二)MyBatis Plus优化策略 1. 分页插件优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor (); paginationInterceptor.setMaxLimit(1000L ); paginationInterceptor.setOverflow(false ); paginationInterceptor.setOptimizeJoin(true ); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }
2. 条件构造器优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Service public class OptimizedUserService extends ServiceImpl <UserMapper, User> { private final ThreadLocal<LambdaQueryWrapper<User>> wrapperCache = ThreadLocal.withInitial(() -> new LambdaQueryWrapper <>()); public List<User> findUsersByCondition (UserQuery query) { LambdaQueryWrapper<User> wrapper = wrapperCache.get(); wrapper.clear(); return list(wrapper .eq(Objects.nonNull(query.getStatus()), User::getStatus, query.getStatus()) .like(StringUtils.isNotBlank(query.getUsername()), User::getUsername, query.getUsername()) .between(Objects.nonNull(query.getStartTime()) && Objects.nonNull(query.getEndTime()), User::getCreateTime, query.getStartTime(), query.getEndTime()) .orderByDesc(User::getCreateTime)); } }
3. 批量操作优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Service public class BatchOptimizedService extends ServiceImpl <UserMapper, User> { @Transactional(rollbackFor = Exception.class) public boolean saveBatchOptimized (List<User> users) { if (CollectionUtils.isEmpty(users)) { return true ; } int batchSize = 1000 ; return saveBatch(users, batchSize); } @Transactional(rollbackFor = Exception.class) public boolean updateBatchOptimized (List<User> users) { if (CollectionUtils.isEmpty(users)) { return true ; } return updateBatchById(users, 1000 ); } }
4. 字段策略优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @TableName("users") public class User { @TableId(type = IdType.ASSIGN_ID) private Long id; @TableField(value = "username", strategy = FieldStrategy.NOT_EMPTY) private String username; @TableField(value = "email", strategy = FieldStrategy.NOT_NULL) private String email; @TableField(exist = false) private String tempField; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }
(三)MyBatis Plus Join优化策略 1. 连表查询优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Service public class JoinOptimizedService { public List<UserOrderVO> getUserOrdersOptimized (Long userId) { return mpjLambdaWrapper() .selectAll(User.class) .selectCollection(Order.class, UserOrderVO::getOrders) .leftJoin(Order.class, Order::getUserId, User::getId) .eq(User::getId, userId) .list(UserOrderVO.class); } public IPage<UserOrderVO> getUserOrdersPage (Page<UserOrderVO> page, UserOrderQuery query) { return mpjLambdaWrapper() .selectAll(User.class) .select(Order::getOrderNo, Order::getAmount, Order::getStatus) .leftJoin(Order.class, Order::getUserId, User::getId) .eq(Objects.nonNull(query.getUserId()), User::getId, query.getUserId()) .eq(Objects.nonNull(query.getOrderStatus()), Order::getStatus, query.getOrderStatus()) .orderByDesc(Order::getCreateTime) .page(page, UserOrderVO.class); } }
2. 查询字段优化 1 2 3 4 5 6 7 8 9 public List<UserSimpleVO> getUserSimpleList () { return mpjLambdaWrapper() .select(User::getId, User::getUsername, User::getEmail) .select(Profile::getAvatar, Profile::getNickname) .leftJoin(Profile.class, Profile::getUserId, User::getId) .eq(User::getStatus, 1 ) .list(UserSimpleVO.class); }
3. 缓存策略优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @Service @CacheConfig(cacheNames = "userCache") public class CachedJoinService { @Autowired private RedisTemplate<String, Object> redisTemplate; private final Cache<String, UserDetailVO> localCache = Caffeine.newBuilder() .maximumSize(1000 ) .expireAfterWrite(Duration.ofMinutes(5 )) .build(); @Cacheable(key = "#userId", unless = "#result == null") public UserDetailVO getUserDetail (Long userId) { UserDetailVO result = localCache.getIfPresent("user:" + userId); if (result != null ) { return result; } result = (UserDetailVO) redisTemplate.opsForValue().get("user:" + userId); if (result != null ) { localCache.put("user:" + userId, result); return result; } result = mpjLambdaWrapper() .selectAll(User.class) .selectAll(Profile.class) .selectCollection(Order.class, UserDetailVO::getRecentOrders, order -> order.limit(5 )) .leftJoin(Profile.class, Profile::getUserId, User::getId) .leftJoin(Order.class, Order::getUserId, User::getId) .eq(User::getId, userId) .orderByDesc(Order::getCreateTime) .one(UserDetailVO.class); if (result != null ) { localCache.put("user:" + userId, result); redisTemplate.opsForValue().set("user:" + userId, result, 30 , TimeUnit.MINUTES); } return result; } @CacheEvict(key = "#userId") public void updateUserCache (Long userId) { localCache.invalidate("user:" + userId); redisTemplate.delete("user:" + userId); } }
(四)原生SQL优化策略 1. PreparedStatement优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Repository public class OptimizedNativeSqlDao { @Autowired private JdbcTemplate jdbcTemplate; public List<User> findUsersByCondition (UserQuery query) { StringBuilder sql = new StringBuilder ("SELECT id, username, email, create_time FROM users WHERE 1=1" ); List<Object> params = new ArrayList <>(); if (Objects.nonNull(query.getStatus())) { sql.append(" AND status = ?" ); params.add(query.getStatus()); } if (StringUtils.isNotBlank(query.getUsername())) { sql.append(" AND username LIKE ?" ); params.add("%" + query.getUsername() + "%" ); } sql.append(" ORDER BY create_time DESC LIMIT ?, ?" ); params.add(query.getOffset()); params.add(query.getLimit()); return jdbcTemplate.query(sql.toString(), params.toArray(), new UserRowMapper ()); } }
2. 批量操作优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public void batchInsertOptimized (List<User> users) { String sql = "INSERT INTO users (username, email, status, create_time) VALUES (?, ?, ?, ?)" ; jdbcTemplate.batchUpdate(sql, users, 1000 , (ps, user) -> { ps.setString(1 , user.getUsername()); ps.setString(2 , user.getEmail()); ps.setInt(3 , user.getStatus()); ps.setTimestamp(4 , Timestamp.valueOf(user.getCreateTime())); }); } @Autowired private NamedParameterJdbcTemplate namedJdbcTemplate;public void batchInsertWithNamedParams (List<User> users) { String sql = "INSERT INTO users (username, email, status, create_time) " + "VALUES (:username, :email, :status, :createTime)" ; List<Map<String, Object>> batchValues = users.stream() .map(user -> { Map<String, Object> params = new HashMap <>(); params.put("username" , user.getUsername()); params.put("email" , user.getEmail()); params.put("status" , user.getStatus()); params.put("createTime" , user.getCreateTime()); return params; }) .collect(Collectors.toList()); namedJdbcTemplate.batchUpdate(sql, batchValues.toArray(new Map [0 ])); }
3. 连接池和事务优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Configuration public class DataSourceConfig { @Bean @Primary public DataSource primaryDataSource () { HikariConfig config = new HikariConfig (); config.setJdbcUrl("jdbc:mysql://localhost:3306/test" ); config.setUsername("root" ); config.setPassword("password" ); config.setMinimumIdle(10 ); config.setMaximumPoolSize(50 ); config.setIdleTimeout(300000 ); config.setMaxLifetime(1800000 ); config.setConnectionTimeout(30000 ); config.setValidationTimeout(5000 ); config.setLeakDetectionThreshold(60000 ); config.addDataSourceProperty("cachePrepStmts" , "true" ); config.addDataSourceProperty("prepStmtCacheSize" , "250" ); config.addDataSourceProperty("prepStmtCacheSqlLimit" , "2048" ); config.addDataSourceProperty("useServerPrepStmts" , "true" ); config.addDataSourceProperty("useLocalSessionState" , "true" ); config.addDataSourceProperty("rewriteBatchedStatements" , "true" ); config.addDataSourceProperty("cacheResultSetMetadata" , "true" ); config.addDataSourceProperty("cacheServerConfiguration" , "true" ); config.addDataSourceProperty("elideSetAutoCommits" , "true" ); config.addDataSourceProperty("maintainTimeStats" , "false" ); return new HikariDataSource (config); } }
五、性能测试与基准对比 (一)测试环境配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @SpringBootTest @TestMethodOrder(OrderAnnotation.class) public class PerformanceComparisonTest { @Autowired private UserMapper userMapper; @Autowired private UserService userService; @Autowired private JoinService joinService; @Autowired private JdbcTemplate jdbcTemplate; private static final int TEST_DATA_SIZE = 10000 ; private static final int QUERY_ITERATIONS = 1000 ; @BeforeAll static void setup () { generateTestData(); } }
(二)CRUD操作性能对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Test @Order(1) public void testInsertPerformance () { List<User> users = generateUsers(TEST_DATA_SIZE); long start = System.currentTimeMillis(); userMapper.batchInsertUsers(users); long mybatisTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); userService.saveBatch(users, 1000 ); long mpTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); batchInsertWithJdbc(users); long jdbcTime = System.currentTimeMillis() - start; System.out.println("插入" + TEST_DATA_SIZE + "条记录耗时对比:" ); System.out.println("MyBatis原生: " + mybatisTime + "ms" ); System.out.println("MyBatis Plus: " + mpTime + "ms" ); System.out.println("原生JDBC: " + jdbcTime + "ms" ); } @Test @Order(2) public void testQueryPerformance () { long start = System.currentTimeMillis(); for (int i = 0 ; i < QUERY_ITERATIONS; i++) { userMapper.findUsersByStatus(1 ); } long mybatisTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); for (int i = 0 ; i < QUERY_ITERATIONS; i++) { userService.lambdaQuery().eq(User::getStatus, 1 ).list(); } long mpTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); for (int i = 0 ; i < QUERY_ITERATIONS; i++) { jdbcTemplate.query("SELECT * FROM users WHERE status = ?" , new Object []{1 }, new UserRowMapper ()); } long jdbcTime = System.currentTimeMillis() - start; System.out.println("执行" + QUERY_ITERATIONS + "次查询耗时对比:" ); System.out.println("MyBatis原生: " + mybatisTime + "ms" ); System.out.println("MyBatis Plus: " + mpTime + "ms" ); System.out.println("原生JDBC: " + jdbcTime + "ms" ); }
(三)连表查询性能对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Test @Order(3) public void testJoinQueryPerformance () { int iterations = 100 ; long start = System.currentTimeMillis(); for (int i = 0 ; i < iterations; i++) { userMapper.findUsersWithProfile(1 , new Date ()); } long mybatisTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); for (int i = 0 ; i < iterations; i++) { joinService.findUsersWithOrders(); } long mpjTime = System.currentTimeMillis() - start; start = System.currentTimeMillis(); for (int i = 0 ; i < iterations; i++) { jdbcTemplate.query( "SELECT u.*, p.profile_name FROM users u LEFT JOIN profiles p ON u.id = p.user_id WHERE u.status = ?" , new Object []{1 }, new UserProfileRowMapper ()); } long jdbcTime = System.currentTimeMillis() - start; System.out.println("执行" + iterations + "次连表查询耗时对比:" ); System.out.println("MyBatis原生: " + mybatisTime + "ms" ); System.out.println("MyBatis Plus Join: " + mpjTime + "ms" ); System.out.println("原生JDBC: " + jdbcTime + "ms" ); }
(四)内存使用情况分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Test @Order(4) public void testMemoryUsage () { Runtime runtime = Runtime.getRuntime(); runtime.gc(); long beforeMp = runtime.totalMemory() - runtime.freeMemory(); List<User> mpUsers = userService.list(); long afterMp = runtime.totalMemory() - runtime.freeMemory(); long mpMemory = afterMp - beforeMp; runtime.gc(); long beforeJdbc = runtime.totalMemory() - runtime.freeMemory(); List<User> jdbcUsers = jdbcTemplate.query("SELECT * FROM users" , new UserRowMapper ()); long afterJdbc = runtime.totalMemory() - runtime.freeMemory(); long jdbcMemory = afterJdbc - beforeJdbc; System.out.println("内存使用对比:" ); System.out.println("MyBatis Plus: " + (mpMemory / 1024 / 1024 ) + "MB" ); System.out.println("原生JDBC: " + (jdbcMemory / 1024 / 1024 ) + "MB" ); }
六、框架选择建议 (一)选择决策矩阵 场景 推荐框架 理由 快速原型开发 MyBatis Plus 开发效率最高,代码量最少 企业级应用 MyBatis原生 可控性强,性能可优化 复杂连表查询 MP Join 类型安全,代码简洁 高性能要求 原生SQL 性能最优,无框架开销 大数据量处理 原生SQL 批量操作效率最高 团队协作开发 MyBatis Plus 统一规范,降低学习成本
(二)性能优先级选择 1. 极致性能场景(金融、支付系统) 1 2 3 4 5 6 7 8 9 10 11 12 13 @Repository public class HighPerformancePaymentDao { @Autowired private JdbcTemplate jdbcTemplate; public void batchProcessPayments (List<Payment> payments) { String sql = "INSERT INTO payments (order_id, amount, status, create_time) VALUES (?, ?, ?, ?)" ; jdbcTemplate.batchUpdate(sql, payments, 1000 , this ::setPaymentParams); } }
2. 开发效率优先场景(中小型项目) 1 2 3 4 5 6 7 8 9 10 11 @Service public class ProductService extends ServiceImpl <ProductMapper, Product> { public IPage<Product> getProductPage (ProductQuery query) { return lambdaQuery() .like(StringUtils.isNotBlank(query.getName()), Product::getName, query.getName()) .eq(Objects.nonNull(query.getCategoryId()), Product::getCategoryId, query.getCategoryId()) .page(new Page <>(query.getCurrent(), query.getSize())); } }
3. 复杂查询场景(报表系统) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Select(""" SELECT p.category_id, c.category_name, COUNT(*) as product_count, AVG(p.price) as avg_price, SUM(CASE WHEN p.status = 1 THEN 1 ELSE 0 END) as active_count FROM products p LEFT JOIN categories c ON p.category_id = c.id WHERE p.create_time >= #{startDate} GROUP BY p.category_id, c.category_name HAVING COUNT(*) > #{minCount} ORDER BY product_count DESC """) List<ProductStatistics> getProductStatistics (@Param("startDate") Date startDate, @Param("minCount") Integer minCount) ;
(三)混合使用策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Service public class HybridUserService { @Autowired private UserMapper userMapper; @Autowired private UserService userService; @Autowired private JdbcTemplate jdbcTemplate; public User getUserById (Long id) { return userService.getById(id); } public List<UserStatistics> getUserStatistics (Date startDate, Date endDate) { return userMapper.getUserStatistics(startDate, endDate); } public void batchUpdateUserStatus (List<Long> userIds, Integer status) { String sql = "UPDATE users SET status = ? WHERE id IN (" + String.join("," , Collections.nCopies(userIds.size(), "?" )) + ")" ; List<Object> params = new ArrayList <>(); params.add(status); params.addAll(userIds); jdbcTemplate.update(sql, params.toArray()); } }
七、最佳实践总结 (一)通用优化原则 1. 数据库层面优化 索引优化 :为常用查询字段建立合适的索引SQL优化 :避免SELECT *,使用LIMIT限制结果集连接池配置 :合理配置连接池参数事务管理 :避免长事务,合理使用事务传播机制2. 应用层面优化 缓存策略 :合理使用一级缓存和二级缓存批量操作 :大量数据操作时使用批量处理分页查询 :大结果集使用分页避免内存溢出懒加载 :按需加载关联数据3. 代码层面优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Service public class OptimizedOrderService { public List<OrderVO> getOrdersWithItems_Wrong () { List<Order> orders = orderMapper.selectAll(); return orders.stream().map(order -> { List<OrderItem> items = orderItemMapper.selectByOrderId(order.getId()); return new OrderVO (order, items); }).collect(Collectors.toList()); } public List<OrderVO> getOrdersWithItems_Right () { return orderMapper.selectOrdersWithItems(); } }
(二)性能监控与调优 1. SQL监控配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: datasource: url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver logging: level: com.example.mapper: DEBUG mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0
2. 性能监控工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Component public class PerformanceMonitor { private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitor.class); @Around("@annotation(com.example.annotation.Monitor)") public Object monitor (ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); logger.info("方法 {} 执行耗时: {}ms" , joinPoint.getSignature().getName(), endTime - startTime); return result; } catch (Exception e) { logger.error("方法 {} 执行异常" , joinPoint.getSignature().getName(), e); throw e; } } }
(三)常见性能陷阱 1. 避免的反模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public List<User> findUsers_Wrong (UserQuery query) { return list(new LambdaQueryWrapper <User>() .eq(User::getStatus, query.getStatus()) .like(User::getUsername, query.getUsername())); } private final ThreadLocal<LambdaQueryWrapper<User>> wrapperCache = ThreadLocal.withInitial(() -> new LambdaQueryWrapper <>()); public List<User> findUsers_Right (UserQuery query) { LambdaQueryWrapper<User> wrapper = wrapperCache.get(); wrapper.clear(); return list(wrapper .eq(User::getStatus, query.getStatus()) .like(User::getUsername, query.getUsername())); }
2. 内存泄漏预防 1 2 3 4 5 6 7 8 9 10 11 12 13 @Service public class MemoryOptimizedService { private final Map<String, List<User>> cache = new WeakHashMap <>(); private final ThreadLocal<StringBuilder> sqlBuilder = new ThreadLocal <>(); public void cleanupResources () { sqlBuilder.remove(); } }
八、总结与展望 (一)框架特点总结 框架 开发效率 运行性能 学习成本 适用场景 MyBatis原生 中等 高 低 复杂查询、性能敏感 MyBatis Plus 高 中等 中等 快速开发、标准CRUD MP Join 高 中等 中等 连表查询、类型安全 原生SQL 低 最高 低 极致性能、大数据量
(二)选择建议 新项目推荐 :MyBatis Plus + 原生SQL混合使用性能敏感项目 :MyBatis原生 + 原生SQL快速开发项目 :MyBatis Plus + MP Join大型企业项目 :MyBatis原生为主,其他框架为辅(三)未来发展趋势 智能化SQL生成 :AI辅助SQL优化和生成更好的类型安全 :编译期SQL检查云原生支持 :更好的分布式数据库支持性能监控集成 :内置性能分析工具(四)实践建议 根据项目特点选择合适的框架组合 建立性能基准测试,定期监控性能指标 制定团队开发规范,统一使用方式 持续学习新特性,及时升级框架版本 通过合理选择和优化MyBatis生态系统中的各种框架,可以在开发效率和运行性能之间找到最佳平衡点,构建出高质量的Java应用系统。
参考资料 官方文档
MyBatis官方文档 MyBatis Plus官方文档 MyBatis Plus Join官方文档 性能优化相关
《高性能MySQL》- 数据库优化 《Java性能调优实战》- JVM和应用优化 HikariCP连接池优化指南 技术文章
MyBatis源码分析系列 MyBatis Plus最佳实践 JDBC性能优化技巧 开源项目
MyBatis官方示例项目 MyBatis Plus代码生成器 企业级项目最佳实践案例