MyBatis N+1问题的5种解决方案
创作时间:
作者:
@小白创作中心
MyBatis N+1问题的5种解决方案
引用
CSDN
1.
https://blog.csdn.net/kaka_buka/article/details/139439727
什么是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 JOIN或INNER JOIN等连接操作。例如:
<select id="getOrdersWithItems" resultMap="orderWithItemsResultMap">
SELECT o.*, i.* FROM orders o
LEFT 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官方文档
热门推荐
本山绿泥适合泡哪些茶叶?
卡皮巴拉,温顺的巨人,真的会咬人吗?
摄影摄像技术全攻略:从入门到高手,最实用的学习指南!
汽车百科:长途驾驶全攻略
一文读懂自动驾驶传感器之激光雷达、毫米波雷达、超声波雷达及摄像头
英语短语辨析:“in the tree”与“on the tree”的区别
初学者必备:高效科学的减脂健身计划详解
晚上忽然肚子疼想吐怎么办
侯宗原:易经智慧在现代生活中的应用
中国传统五行理论衍生的饰品之五行手串
探究越野摩托中的跑车,速度与激情的极致融合
如何通过饮食调理皮肤健康?哪些食物有助于护肤美颜?
感冒刚好的那一批朋友们,别着急运动呐!
感冒时能不能运动?记住这条“颈部以上”原则!
无监督学习:自编码器(AutoEncoder)
无限恐怖漫画:探索恐惧边界的极致之旅
揭秘!读博背后的心理健康危机
如何设置NVIDIA显卡以达到最高性能(详解NVIDIA显卡性能优化技巧)
林则徐虎门销烟的历史壮举
《咒术回战》实力排行榜 TOP10:术式、战绩与潜力的终极较量
最全的西安赏樱花地点——在这个月底盛开,看看你是否都想去?
猫咪为什么在地上打滚?
和女朋友聊天的20个绝佳话题,让感情迅速升温!
在香港注册时,通常选择哪种电子邮件服务提供商?
老是呼吸不上来怎么办?必须的深呼吸才能喘过气来
《海贼王》草帽团冒险地图:40 座岛屿的热血与泪水!
自动挡VS手动挡,油耗大战,谁才是省油王者?
姜枣普洱茶的健康益处、功效作用及饮用禁忌
晚上睡觉到底要不要关灯?开灯睡觉有危害吗?
电脑的睡眠和休眠模式有何不同?一文详解两种节能状态