Mybatis中一对多查询只有一条数据问题

探讨MyBatis中mapper.xml配置下一对多查询仅返回单条数据的问题,涉及XML映射、实体类设计及查询逻辑,分析可能的原因并提供解决思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Mybatis的mapper.xml中一对多查询结果的问题

问题描述:只能查询出一条数据

xml文件的配置

//<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="pm.mapper.PersonMapper">
	<resultMap type="pm.entity.Person" id="selectPAC">
		<id column="Pid" property="Pid"/>
       <result column="Pname" property="Pname"/>
       <result column="Page" property="Page"/>
       <result column="Sex" property="Sex"/>
       <collection property="list" ofType="pm.entity.Car">
           <id column="Cid" property="Cid"/>
           <result column="color" property="color"/>
           <result column="brand" property="brand"/>
           <result column="price" property="price"/>
       </collection>
	</resultMap>
	<select id="selectPerAndCar"  resultMap="selectPAC">
		select * from Person,Car where person.Pid=Car.Cid 
	</select>
</mapper>

**
它们的关系是这样的:person里面包含着一个Car集合;
Class Person

package pm.entity;
import java.util.List;

public class Person {	
	private int Pid;
	private String Pname;
	private int Page;
	private String Sex;
	private List<Car>list;

	public int getPid() {
		return Pid;
	}
	public void setPid(int pid) {
		Pid = pid;
	}
	public String getPname() {
		return Pname;
	}
	public void setPname(String pname) {
		Pname = pname;
	}
	public int getPage() {
		return Page;
	}
	public void setPage(int page) {
		Page = page;
	}
	public String getSex() {
		return Sex;
	}
	public void setSex(String sex) {
		Sex = sex;
	}
	public List<Car> getList() {
		return list;
	}
	public void setList(List<Car> list) {
		this.list = list;
	}
	@Override
	public String toString() {
		return "Person [Pid=" + Pid + ", Pname=" + Pname + ", Page=" + Page + ", Sex=" +Sex + "]";
	}
}

Class Car

package pm.entity;

public class Car {
	private int Cid;
	private String color;
	private int price;
	private String brand;
	//private Person per;
	public Car() {
		super();
	}
	
	public int getCid() {
		return Cid;
	}
	public void setCid(int cid) {
		Cid = cid;
	}
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
//	public Person getPer() {
//		return per;
//	}
//	public void setPer(Person per) {
//		this.per = per;
//	}

	public String toString() {
		return "Car [Cid=" + Cid + ", color=" + color + ", price=" + price + ", brand=" + brand  + "]";
	}
}
# 测试代码块

**`
```javascript
var foo = 'bar';
	public static void main(String[] args) throws IOException {
		String resource="SqlMapConfig.xml";
		InputStream iS=Resources.getResourceAsStream(resource);
		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(iS);
		SqlSession sqlSession = factory.openSession();
		PersonMapper mapper = sqlSession.getMapper(pm.mapper.PersonMapper.class);
		List<Person> p=mapper.selectPerAndCar();
        System.out.println(p.get(1));
        List<Car> list = p.get(1).getList();
			for (Car car : list) {
				System.out.println(car);
			}	
		sqlSession.commit();
		sqlSession.close();
	}

输出结果
Person [Pid=2, Pname=tim, Page=25, Sex=男]
Car [Cid=2, color=cyan, price=999999, brand=Lincoln]

Car里面应该有多条数据,只输出了第一条。

原因分析

如果两表联查,主表和明细表的主键都是id的话,明细表的多条只能查询出来第一条,但这个名字不一样,应该不是这方面问题
<think>我们正在处理MyBatis一对多映射的性能优化问题。根据引用内容,我们了解到使用`<collection>`标签进行一对多映射时,可能会遇到性能问题,特别是当主查询返回多条记录时,每一条记录都会触发一次子查询(N+1问题)。因此,我们需要优化这种映射方式。 常见的优化方法包括: 1. 使用嵌套结果映射(连接查询)代替嵌套查询(子查询)。 2. 使用批量查询(Batch Loading)来减少数据库交互次数。 3. 在业务层进行分步查询并手动组装数据(适用于复杂场景)。 根据引用[2]中的例子,原始配置使用了子查询: ```xml <resultMap id="rmGoods1" type="Goods"> <id column="goods_id" property="goodsId"/> <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/> </resultMap> ``` 这种方式会导致N+1问题:首先查询主表(t_goods)得到N条记录,然后每条记录根据goods_id去执行子查询(goodsDetail.selectByGoodsId),共执行N次查询。 优化方案一:使用连接查询(嵌套结果映射) 我们可以通过一次SQL连接查询同时获取主表和子表的数据,然后在resultMap中配置嵌套结果映射。这样只需要一次数据库交互。 例如: ```xml <resultMap id="rmGoodsJoin" type="Goods"> <id column="goods_id" property="goodsId"/> <!-- 其他主表字段映射 --> <result column="goods_name" property="goodsName"/> <!-- 一对多映射使用嵌套结果 --> <collection property="goodsDetails" ofType="GoodsDetail"> <id column="detail_id" property="id"/> <result column="detail_name" property="detailName"/> <!-- 其他子表字段映射 --> </collection> </resultMap> <select id="selectOneToManyJoin" resultMap="rmGoodsJoin"> SELECT g.goods_id, g.goods_name, d.detail_id, d.detail_name FROM t_goods g LEFT JOIN t_goods_detail d ON g.goods_id = d.goods_id LIMIT 0,10 </select> ``` 注意:连接查询可能会导致结果集行数增加(主表每行对应子表多行),MyBatis会根据主表的主键进行分组,将子表的数据收集到集合中。 优化方案二:使用MyBatis的批量加载功能(懒加载+批量执行) 在MyBatis的全局配置中,可以设置`aggressiveLazyLoading`为false(默认已经是false,但需要开启懒加载)并设置`lazyLoadingEnabled`为true,然后使用`fetchType="lazy"`。此外,还可以设置`batchExecutor`来批量执行延迟加载的语句。 步骤: 1. 在MyBatis配置文件中(如mybatis-config.xml)启用懒加载和批量执行器: ```xml <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <!-- 使用批量执行器 --> <setting name="defaultExecutorType" value="BATCH"/> </settings> ``` 或者,也可以在获取SqlSession时指定执行器类型。 2. 在映射文件中,我们可以继续使用嵌套查询(select方式),但设置`fetchType="lazy"`(延迟加载)并利用批量执行器来优化多次查询变为批量查询。 ```xml <resultMap id="rmGoods1" type="Goods"> <id column="goods_id" property="goodsId"/> <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id" fetchType="lazy"/> </resultMap> ``` 这样,当访问goodsDetails属性时,MyBatis会延迟加载。并且,如果在同一次操作中需要加载多个Goods对象的goodsDetails,MyBatis会将这些延迟加载的查询合并成批量查询(前提是使用Batch执行器)。 注意:批量加载需要数据库驱动支持批量操作,并且批量执行器会缓存多个Statement,在调用commit、close或flushStatements时才会执行。 优化方案三:分步查询,手动控制(在业务层进行) 我们可以在业务层先查询主表数据,然后收集所有需要查询子表的主键,一次性查询所有子表数据,再手动组装到主对象中。这样可以精确控制查询次数(2次查询)。 例如: 步骤1:查询主表数据(List<Goods>) 步骤2:从主表数据中提取所有goods_id 步骤3:根据goods_id列表查询所有关联的GoodsDetail(一次查询,使用IN语句) 步骤4:将查询到的GoodsDetail按goods_id分组,然后设置到对应的Goods对象中。 总结: 1. 如果数据量不大,且关联关系简单,推荐使用连接查询(优化方案一),因为一次查询即可获取所有数据。 2. 如果数据量较大,或者关联的子表数据量很大,连接查询可能产生大量冗余数据(笛卡尔积),这时使用延迟加载+批量执行(优化方案二)或分步查询(优化方案三)更合适。 根据实际情况选择优化方案。 相关问题: 1. 如何避免MyBatis一对多映射中的N+1查询问题? 2. MyBatis中嵌套结果映射和嵌套查询的区别? 3. 如何配置MyBatis的批量执行器来优化延迟加载?</think>### MyBatis 一对多映射中 `<collection>` 的 SELECT 查询性能优化方案 #### 1. **避免 N+1 查询问题(核心优化)** 当使用嵌套 `select` 查询时(如下所示),会导致 **N+1 查询问题**: ```xml <collection property="goodsDetails" select="selectDetails" column="goods_id"/> ``` - **问题**:主查询返回 N 条记录时,会触发 N 次子查询,性能急剧下降。 - **优化方案**:改用 **JOIN 查询 + 嵌套结果映射**: ```xml <resultMap id="goodsWithDetailsMap" type="Goods"> <id column="goods_id" property="id"/> <!-- 主表字段映射 --> <collection property="goodsDetails" ofType="GoodsDetail"> <id column="detail_id" property="id"/> <!-- 子表字段映射 --> </collection> </resultMap> <select id="selectGoods" resultMap="goodsWithDetailsMap"> SELECT g.*, d.* FROM goods g LEFT JOIN goods_detail d ON g.goods_id = d.goods_id </select> ``` **优势**:单次查询完成数据加载,减少数据库交互次数[^2]。 --- #### 2. **启用延迟加载(Lazy Loading)** 当子数据非立即需要时: ```xml <!-- 全局配置 --> <settings> <setting name="lazyLoadingEnabled" value="true"/> </settings> <!-- 局部配置 --> <collection property="goodsDetails" select="selectDetails" column="goods_id" fetchType="lazy" <!-- 按需加载子数据 --> /> ``` **效果**:首次只加载主实体,访问子集合时才触发查询[^4]。 --- #### 3. **批量加载(Batch Fetch)** 通过 `@Options` 注解或全局配置启用批量执行器: ```java @Options(fetchSize = 500) // 设置批量加载大小 List<Goods> selectGoodsBatch(); ``` ```xml <!-- mybatis-config.xml --> <settings> <setting name="defaultExecutorType" value="BATCH"/> </settings> ``` **原理**:将多个子查询合并为批量操作,减少网络开销[^2]。 --- #### 4. **分步查询优化** 若必须使用嵌套 `select`,通过 `column` 传递多参数: ```xml <collection property="goodsDetails" select="selectDetails" column="{goodsId=goods_id, status=active}" <!-- 传递多个参数 --> /> ``` 子查询 SQL: ```xml <select id="selectDetails" resultType="GoodsDetail"> SELECT * FROM goods_detail WHERE goods_id = #{goodsId} AND status = #{status} </select> ``` **作用**:精确控制子查询条件,避免冗余数据[^2]。 --- #### 5. **限制子查询数据量** 在子查询中添加分页或条件限制: ```xml <collection property="goodsDetails" select="selectTopDetails" <!-- 仅查询必要数据 --> column="goods_id" /> <!-- 子查询示例 --> <select id="selectTopDetails" resultType="GoodsDetail"> SELECT * FROM goods_detail WHERE goods_id = #{goods_id} ORDER BY create_time DESC LIMIT 10 <!-- 限制子集大小 --> </select> ``` --- #### 6. **缓存优化** 启用二级缓存减少重复查询: ```xml <!-- 主Mapper配置 --> <cache eviction="LRU" flushInterval="60000"/> <!-- 子查询Mapper --> <select id="selectDetails" resultType="GoodsDetail" useCache="true"> ... </select> ``` **适用场景**:读多写少的业务[^1]。 --- ### 性能对比总结 | 方案 | 查询次数 | 适用场景 | |---------------------|----------|------------------------------| | JOIN + 嵌套结果映射 | 1 | 子数据量适中(推荐) | | 延迟加载 | 1+N(按需)| 子数据非立即使用 | | 批量加载 | 1+1(批量)| 必须使用嵌套查询时 | | 原始嵌套查询 | 1+N | 严禁在大数据量使用 | > **最佳实践**:优先使用 JOIN 查询,必要时结合延迟加载和批量执行。当子数据超过 1000 行时,建议分步查询手动组装数据[^2][^4]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值