典型的**N+1查询问题**

典型的N+1查询问题:当需要查询主实体及其关联子实体时,传统做法是先查询主实体,再循环查询每个主实体对应的子实体,导致数据库查询次数为 1(主查询) + N(子查询),性能较差。

优化方案示例

以下是一个完整示例,包括表结构、实体类、Mapper接口和优化后的查询代码:

1. MySQL表结构
-- 主表:订单
CREATE TABLE `orders` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) NOT NULL COMMENT '订单号',
  `total_amount` decimal(10,2) DEFAULT NULL COMMENT '总金额',
  PRIMARY KEY (`id`)
);

-- 子表:订单详情
CREATE TABLE `order_items` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_id` bigint NOT NULL COMMENT '订单ID',
  `product_name` varchar(100) DEFAULT NULL COMMENT '商品名称',
  `quantity` int DEFAULT NULL COMMENT '数量',
  PRIMARY KEY (`id`),
  KEY `idx_order_id` (`order_id`)
);
2. 实体类
// 订单实体
public class Order {
    private Long id;
    private String orderNo;
    private BigDecimal totalAmount;
    private List<OrderItem> items; // 订单详情列表
    
    // getters/setters
}

// 订单详情实体
public class OrderItem {
    private Long id;
    private Long orderId;
    private String productName;
    private Integer quantity;
    
    // getters/setters
}
3. MyBatis Mapper接口
public interface OrderMapper {
    // 查询单个订单
    Order selectOrderById(Long id);
    
    // 批量查询订单详情
    List<OrderItem> selectItemsByOrderIds(@Param("orderIds") List<Long> orderIds);
}
4. MyBatis XML映射文件
<!-- OrderMapper.xml -->
<mapper namespace="com.example.mapper.OrderMapper">
    <select id="selectOrderById" resultType="com.example.entity.Order">
        SELECT * FROM orders WHERE id = #{id}
    </select>
    
    <select id="selectItemsByOrderIds" resultType="com.example.entity.OrderItem">
        SELECT * FROM order_items 
        WHERE order_id IN 
        <foreach collection="orderIds" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
</mapper>
5. 优化后的Service代码
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    
    public Order getOrderWithItems(Long orderId) {
        // 1. 查询主订单
        Order order = orderMapper.selectOrderById(orderId);
        if (order == null) {
            return null;
        }
        
        // 2. 批量查询订单详情(关键优化点)
        List<OrderItem> items = orderMapper.selectItemsByOrderIds(Collections.singletonList(orderId));
        
        // 3. 关联数据
        order.setItems(items);
        return order;
    }
}

优化前后对比

优化前(N+1查询):

// 传统N+1查询方式(不推荐)
public Order getOrderWithItemsBad(Long orderId) {
    Order order = orderMapper.selectOrderById(orderId);
    if (order != null) {
        // 循环查询每个订单的详情(导致N次查询)
        List<OrderItem> items = new ArrayList<>();
        for (Long itemId : order.getItemIds()) {
            OrderItem item = itemMapper.selectById(itemId);
            items.add(item);
        }
        order.setItems(items);
    }
    return order;
}

优化后(批量查询):

// 优化后的批量查询方式
public Order getOrderWithItems(Long orderId) {
    Order order = orderMapper.selectOrderById(orderId);
    if (order != null) {
        // 批量查询所有详情(只需1次查询)
        List<OrderItem> items = itemMapper.selectByOrderIds(
            Collections.singletonList(orderId));
        order.setItems(items);
    }
    return order;
}

核心优化思路

  1. 减少数据库交互次数:从多次查询变为两次查询(一次主查询 + 一次批量子查询)

  2. 利用集合操作替代循环查询

    • 先查询主实体
    • 提取所有关联ID
    • 使用 IN 语句一次性查询所有关联子实体
    • 通过Map分组快速关联数据
  3. MyBatis关键配置

    • 使用 <foreach> 标签处理批量查询
    • 通过 @Param 注解传递参数列表
    • 确保子表有合适的索引(如示例中的 idx_order_id

这种优化方案在数据量越大时效果越明显,尤其适合一对多关联查询场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值