MyBatis常见问题与解决方案实战指南

作为Java开发中最流行的ORM框架之一,MyBatis在实际应用中经常会遇到一些典型问题。本文将深入分析5个最常见的技术挑战,并提供经过生产验证的解决方案。

1. N+1查询问题:性能杀手与解决方案

问题本质

N+1查询问题是指当查询主对象时,对每个主对象关联的子对象都产生一次额外查询。例如查询10个订单,每个订单又查询一次订单项,总共产生11次查询(1次查订单+10次查订单项)。

图1

解决方案

方案一:使用JOIN查询+结果映射

<resultMap id="orderWithItems" type="Order">
    <id property="id" column="order_id"/>
    <collection property="items" ofType="OrderItem" 
                resultMap="orderItemResultMap"/>
</resultMap>

<select id="getOrdersWithItems" resultMap="orderWithItems">
    SELECT o.*, i.* 
    FROM orders o LEFT JOIN order_items i ON o.id = i.order_id
    WHERE o.user_id = #{userId}
</select>

方案二:开启全局延迟加载

<!-- mybatis-config.xml -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

实践建议

  • 对于关联数据量小的场景使用JOIN查询
  • 大数据量关联时使用延迟加载+按需加载
  • 监控SQL日志,使用@FetchSize优化大批量查询

2. 事务管理:Spring集成最佳实践

声明式事务配置

@Configuration
@EnableTransactionManagement
public class MyBatisConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

编程式事务示例

public void batchProcessOrders(List<Order> orders) {
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.execute(status -> {
        try {
            orders.forEach(orderMapper::update);
            return true;
        } catch (Exception e) {
            status.setRollbackOnly();
            throw e;
        }
    });
}

常见陷阱

  • 方法内部调用导致@Transactional失效
  • 错误的事务传播级别设置
  • MyBatis一级缓存与事务边界不一致

最佳实践

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderMapper orderMapper;
    
    @Transactional(propagation = Propagation.REQUIRED, 
                   isolation = Isolation.READ_COMMITTED,
                   timeout = 30)
    public void processOrder(Order order) {
        // 业务逻辑
    }
}

3. 主键生成策略全解析

常用策略对比

策略类型示例配置适用场景优缺点
数据库自增useGeneratedKeys="true"MySQL、PostgreSQL简单但批量插入效率低
UUID应用层生成分布式系统全局唯一但无序
序列(Sequence)<selectKey>调用序列Oracle高并发下可能有性能瓶颈
雪花算法自定义TypeHandler分布式系统趋势递增但依赖系统时钟

典型配置示例

数据库自增ID

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO users(name) VALUES(#{name})
</insert>

Oracle序列

<insert id="insertProduct">
    <selectKey keyProperty="id" resultType="long" order="BEFORE">
        SELECT product_seq.nextval FROM dual
    </selectKey>
    INSERT INTO products(id, name) VALUES(#{id}, #{name})
</insert>

批量插入优化

// 使用BatchExecutor
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    for (User user : userList) {
        mapper.insert(user);
    }
    session.commit();
}

4. 分页实现:从基础到高级

物理分页 vs 逻辑分页

图2

PageHelper实现方案

// Spring Boot配置
@Configuration
public class MyBatisConfig {
    @Bean
    public PageInterceptor pageInterceptor() {
        return new PageInterceptor();
    }
}

// 使用示例
public PageInfo<User> getUsers(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectAll();
    return new PageInfo<>(users);
}

手写分页SQL(MySQL)

<select id="selectByPage" resultType="User">
    SELECT * FROM users
    ORDER BY create_time DESC
    LIMIT #{offset}, #{pageSize}
</select>

性能优化建议

  • 避免COUNT(*)全表扫描,使用覆盖索引
  • 大数据量分页使用"seek method"(where id > ? limit ?)
  • 分页结果缓存策略设计

5. 多数据源处理:企业级解决方案

动态数据源路由

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

// 使用AOP切换数据源
@Around("@annotation(dataSource)")
public Object switchDataSource(ProceedingJoinPoint pjp, DataSource dataSource) throws Throwable {
    DataSourceContextHolder.set(dataSource.value());
    try {
        return pjp.proceed();
    } finally {
        DataSourceContextHolder.clear();
    }
}

Spring Boot多数据源配置

# application.yml
spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/db1
      username: user1
      password: pass1
    secondary:
      url: jdbc:mysql://localhost:3306/db2
      username: user2
      password: pass2

事务管理要点

  • 使用@Transactional(transactionManager = "primaryTransactionManager")指定事务管理器
  • 避免跨数据源事务(考虑分布式事务方案)
  • 连接池参数根据业务特点单独配置

总结与进阶建议

  1. 性能监控:集成Micrometer或自定义拦截器监控SQL性能
  2. 安全审计:定期检查SQL注入风险点,特别是动态SQL部分
  3. 版本升级:关注MyBatis新特性如:

    • 3.5+版本的Kotlin DSL支持
    • 增强的批量操作API
    • 改进的延迟加载机制

通过合理应用这些解决方案,可以显著提升MyBatis在生产环境中的稳定性和性能表现。建议根据具体业务场景进行组合使用,并建立相应的监控机制。

添加新评论