当前位置: 首页 > news >正文

解决MyBatis的N+1问题

解决MyBatis的N+1问题

N+1问题通常出现在一对多关联查询中。当我们查询主表数据(如订单)并希望获取关联的从表数据(如订单的商品)时,如果每获取一条主表记录都要执行一次从表查询,就会产生N+1次查询的问题。假设有10个订单,主查询执行1次,从查询执行10次,总共执行了11次查询。这种情况显然会导致性能低下。

这个问题比较傻,可能只有刚接触编程的人才会犯这么初级的错误,最近在面试的过程中被问到了这个问题,给我搞的一愣一愣的。所以记录一下。

示例

假设我们有两个表:orders(订单表)和items(商品表),一个订单可以有多个商品。传统的MyBatis配置可能会这样写:

<select id="getOrders" resultMap="orderResultMap">SELECT * FROM orders
</select><select id="getItemsByOrderId" resultMap="itemResultMap" parameterType="int">SELECT * FROM items WHERE order_id = #{orderId}
</select>

在Java代码中调用:

List<Order> orders = orderMapper.getOrders();
for (Order order : orders) {List<Item> items = orderMapper.getItemsByOrderId(order.getId());order.setItems(items);
}

这种方式会导致N+1问题。

解决方法

1. 使用嵌套查询(Subqueries)

嵌套查询通过在一个查询中嵌套其他查询,可以减少查询次数。这个方法通常在SQL语句中使用IN子句。例如:

<select id="getOrdersWithItems" resultMap="orderWithItemsResultMap">SELECT * FROM orders WHERE id IN(SELECT DISTINCT order_id FROM items WHERE order_id IS NOT NULL)
</select><resultMap id="orderWithItemsResultMap" type="Order"><id property="id" column="id"/><result property="orderName" column="order_name"/><collection property="items" ofType="Item"><id property="id" column="item_id"/><result property="itemName" column="item_name"/><result property="orderId" column="order_id"/></collection>
</resultMap>

2. 使用JOIN查询

JOIN查询通过一次性获取所有需要的数据,避免了多次查询的问题。这个方法通常在SQL语句中使用LEFT JOININNER JOIN等连接操作。例如:

<select id="getOrdersWithItems" resultMap="orderWithItemsResultMap">SELECT o.*, i.* FROM orders oLEFT JOIN items i ON o.id = i.order_id
</select><resultMap id="orderWithItemsResultMap" type="Order"><id property="id" column="id"/><result property="orderName" column="order_name"/><collection property="items" ofType="Item"><id property="id" column="item_id"/><result property="itemName" column="item_name"/><result property="orderId" column="order_id"/></collection>
</resultMap>

3. 使用批量查询(Batch Query)

批量查询可以将多个查询合并为一个查询,减少查询次数。例如:

<select id="getOrders" resultMap="orderResultMap">SELECT * FROM orders
</select><select id="getItemsByOrderIds" resultMap="itemResultMap" parameterType="list">SELECT * FROM items WHERE order_id IN<foreach item="orderId" collection="list" open="(" separator="," close=")">#{orderId}</foreach>
</select>

在Java代码中批量查询:

List<Order> orders = orderMapper.getOrders();
List<Integer> orderIds = orders.stream().map(Order::getId).collect(Collectors.toList());
List<Item> items = orderMapper.getItemsByOrderIds(orderIds);// 处理查询结果,将items分配给对应的order
Map<Integer, List<Item>> itemsMap = items.stream().collect(Collectors.groupingBy(Item::getOrderId));
for (Order order : orders) {order.setItems(itemsMap.get(order.getId()));
}

4. 使用缓存(Caching)

缓存可以减少数据库的查询次数,特别是在数据变化不频繁的情况下。MyBatis提供了一级缓存和二级缓存机制。例如:

<settings><setting name="cacheEnabled" value="true"/>
</settings><cache/>

在Mapper文件中使用缓存:

<cache/>
<select id="getOrders" resultMap="orderResultMap" useCache="true">SELECT * FROM orders
</select><select id="getItemsByOrderId" resultMap="itemResultMap" parameterType="int" useCache="true">SELECT * FROM items WHERE order_id = #{orderId}
</select>

5. 使用懒加载(Lazy Loading)

MyBatis支持懒加载,当访问到关联对象时才执行查询。可以通过以下方式开启懒加载:

<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>

在Mapper文件中设置:

<resultMap id="orderResultMap" type="Order"><id property="id" column="id"/><result property="orderName" column="order_name"/><association property="items" javaType="List" select="getItemsByOrderId" fetchType="lazy"/>
</resultMap>

参考链接

  • MyBatis官方文档

在这里插入图片描述

http://www.lryc.cn/news/362675.html

相关文章:

  • 12-学生们参加各科测试的次数(高频 SQL 50 题基础版)
  • 2024网络与信息安全管理员职工职业技能竞赛re0220164094
  • Elasticsearch--easy-ES框架使用,轻松操作查询Elasticsearch,简化开发
  • 【教程】如何实现WordPress网站降级(用于解决插件和主题问题)
  • 思维导图-vb.net开发带进度条的复制文件夹功能c#复制文件夹
  • Linux文本处理三剑客之awk命令
  • 公差和配合
  • AI大模型应用开发实践:5.快速入门 Assistants API
  • stack和queue的模拟实现
  • 你的手机是如何控制你的手表之广播篇
  • 深入理解并发之LongAdder、DoubleAdder的实现原理
  • virtuoso原理图无法编辑
  • Kotlin协程中的作用域 `GlobalScope`、`lifecycleScope` 和 `viewModelScope`
  • leetcode739 每日温度
  • 【软件测试】自动化测试如何管理测试数据
  • Llama 3-V: 比GPT4-V小100倍的SOTA
  • Anaconda安装配置
  • 全面理解渗透测试
  • 「网络编程」基于 UDP 协议实现回显服务器
  • 云数融合与大数据技术在日常生活中的创新应用探索
  • ETCD 备份与还原
  • LeeCode 1787 DP
  • 如何有效屏蔽手机上的骚扰电话20240530
  • Linux CGroup资源限制(概念限制进程CPU使用)
  • Latex中标注通讯作者
  • PyQt5开发笔记:1.环境搭建与界面美化
  • 公派/自费访问学者申请出国访学的常见问题解答(下)
  • 完全指南:C语言学习资源汇总
  • Kubernetes——Ingress详解
  • 反射、类加载、静态代理,jdk动态代理,cglib代理