MyBatis 一对一、一对多 SQL + 完整示例(MySQL)
说明:两种写法:关联查询(join,一次 SQL) 、嵌套查询(子查询,多条 SQL),日常开发 join 用的更多 实体、Mapper 接口、xml、SQL 全部给完整可运行例子
表设计
java
-- 用户表(主表)
CREATE TABLE t_user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
age INT
);
-- 用户详情表:一对一 【一个用户 对应 一条详情】
CREATE TABLE t_user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT UNIQUE, -- 唯一,一对一关键
address VARCHAR(100),
phone VARCHAR(20)
);
-- 订单表:一对多 【一个用户 对应 多个订单】
CREATE TABLE t_order (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
order_no VARCHAR(32),
amount DECIMAL(10,2)
);
一、一对一:User → UserInfo
实体类
java
// User.java
@Data
public class User {
private Integer id;
private String username;
private Integer age;
// 一对一:用户详情
private UserInfo userInfo;
}
// UserInfo.java
@Data
public class UserInfo {
private Integer id;
private Integer userId;
private String address;
private String phone;
}
Mapper 接口
java
public interface UserMapper {
// 根据id查询用户(带一对一详情)
User findUserWithInfoById(Integer id);
}
Mapper.xml 【方式 1:JOIN 关联查询(推荐,单条 SQL)】
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="com.mapper.UserMapper">
<!-- 一对一结果映射 -->
<resultMap id="UserWithInfoMap" type="User">
<id column="u_id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<!-- 一对一 association -->
<association property="userInfo" javaType="UserInfo">
<id column="info_id" property="id"/>
<result column="user_id" property="userId"/>
<result column="address" property="address"/>
<result column="phone" property="phone"/>
</association>
</resultMap>
<select id="findUserWithInfoById" resultMap="UserWithInfoMap">
SELECT
u.id u_id,
u.username,
u.age,
ui.id info_id,
ui.user_id,
ui.address,
ui.phone
FROM t_user u
LEFT JOIN t_user_info ui ON u.id = ui.user_id
WHERE u.id = #{id}
</select>
</mapper>
一对一【方式 2:嵌套子查询(两条 SQL,association + select)】
XML
<resultMap id="UserWithInfoSubMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<!-- 嵌套查询:单独执行findInfoByUserId -->
<association property="userInfo" javaType="UserInfo" select="findInfoByUserId" column="id"/>
</resultMap>
<select id="findUserWithInfoByIdSub" resultMap="UserWithInfoSubMap">
SELECT id,username,age FROM t_user WHERE id=#{id}
</select>
<select id="findInfoByUserId" resultType="UserInfo">
SELECT id,user_id,address,phone FROM t_user_info WHERE user_id = #{id}
</select>
二、一对多:User → List<Order>
实体类
java
@Data
public class User {
private Integer id;
private String username;
private Integer age;
// 一对多:多个订单
private List<Order> orderList;
}
@Data
public class Order {
private Integer id;
private Integer userId;
private String orderNo;
private BigDecimal amount;
}
Mapper 接口
java
public interface UserMapper {
User findUserWithOrderById(Integer id);
}
Mapper.xml 【方式 1:JOIN 关联查询(推荐)】
一对多核心标签:
collection
XML
<mapper namespace="com.mapper.UserMapper">
<resultMap id="UserWithOrderMap" type="User">
<id column="u_id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<!-- 一对多 collection,ofType 指定集合里面元素类型 -->
<collection property="orderList" ofType="Order">
<id column="o_id" property="id"/>
<result column="user_id" property="userId"/>
<result column="order_no" property="orderNo"/>
<result column="amount" property="amount"/>
</collection>
</resultMap>
<select id="findUserWithOrderById" resultMap="UserWithOrderMap">
SELECT
u.id u_id,
u.username,
u.age,
o.id o_id,
o.user_id,
o.order_no,
o.amount
FROM t_user u
LEFT JOIN t_order o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
</mapper>
⚠️注意:join 一对多会返回多条行数据,MyBatis 依靠主表 id 相同自动合并成一个 User,订单放进 List,不需要自己去重。
一对多【方式 2:嵌套子查询(多条 SQL,N+1 问题)】
XML
<resultMap id="UserWithOrderSubMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<!-- column 把user的id传给子查询 -->
<collection property="orderList" ofType="Order" select="findOrderByUserId" column="id"/>
</resultMap>
<select id="findUserWithOrderByIdSub" resultMap="UserWithOrderSubMap">
SELECT id,username,age FROM t_user WHERE id=#{id}
</select>
<select id="findOrderByUserId" resultType="Order">
SELECT id,user_id,order_no,amount FROM t_order WHERE user_id=#{id}
</select>
❗嵌套子查询会产生 N+1:查 1 次用户 + N 次订单,数据量大慎用,可以配置
fetchType="lazy"开启懒加载
关键点对比总结
表格
| 关系 | 标签 | 特点 |
|---|---|---|
| 一对一 | <association javaType="实体类"> |
单个对象 |
| 一对多 | <collection ofType="实体类"> |
List 集合 |
常用坑提醒
- 列名重名!join 查询一定要别名(u_id /o_id),否则映射错乱
- 一对多 join 不要加
DISTINCT,会把订单数据去重丢失 - 嵌套查询懒加载配置(mybatis-config.xml)
XML
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>