<collection> 和 <association>的详细用法(附详细代码解析)
MyBatis 中的 <association>
和 <collection>
标签
在 MyBatis 中,<association>
和 <collection>
标签用于配置复杂查询的结果集关联映射,主要用于处理实体类之间的嵌套关系。二者的区别在于:
<association>
标签用于一对一的关系映射,比如实体类 A 中包含一个实体类 B 的对象。<collection>
标签用于一对多的关系映射,比如实体类 A 中包含一个 B 类对象的集合。
示例:<association>
和 <collection>
标签的区别
我们以下面的场景为例:
- 一个 User 实体类,它有一个 Address 实体类的属性(表示一对一关系)。
- 一个 User 实体类,它有多个 Order 实体类的对象(表示一对多关系)。
数据库表结构
假设有三个表:user
、address
和 orders
,表结构如下:
user
表结构:
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
age INT,
address_id BIGINT
);
address
表结构:
CREATE TABLE address (
id BIGINT PRIMARY KEY,
city VARCHAR(50),
street VARCHAR(100)
);
orders
表结构:
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT,
product_name VARCHAR(100),
price DECIMAL(10, 2)
);
实体类定义
以下是 Java 实体类定义:
User 实体类:
public class User {
private Long id;
private String name;
private int age;
private Address address; // 一对一关系
private List<Order> orders; // 一对多关系
// getters and setters
}
Address 实体类:
public class Address {
private Long id;
private String city;
private String street;
// getters and setters
}
Order 实体类:
public class Order {
private Long id;
private Long userId;
private String productName;
private BigDecimal price;
// getters and setters
}
MyBatis 映射配置
UserMapper.xml
配置
在 UserMapper.xml
中使用 <association>
和 <collection>
标签来映射复杂关系:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 查询用户及其地址和订单信息 -->
<select id="getUserById" resultMap="userResultMap">
SELECT
u.id AS user_id, u.name, u.age,
a.id AS address_id, a.city, a.street,
o.id AS order_id, o.product_name, o.price
FROM user u
LEFT JOIN address a ON u.address_id = a.id
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
<!-- 用户结果映射 -->
<resultMap id="userResultMap" type="com.example.entity.User">
<id property="id" column="user_id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一映射 Address -->
<association property="address" javaType="com.example.entity.Address" columnPrefix="address_" fetchType="lazy" select="com.example.mapper.AddressMapper.getAddressById">
<id property="id" column="address_id"/>
<result property="city" column="city"/>
<result property="street" column="street"/>
</association>
<!-- 一对多映射 Orders -->
<collection property="orders" ofType="com.example.entity.Order" columnPrefix="order_" fetchType="lazy">
<id property="id" column="order_id"/>
<result property="productName" column="product_name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
</mapper>
-
<association>
标签:用于映射 User 和 Address 之间的一对一关系,将 Address 的属性映射到 User 对象中的address
属性上。columnPrefix
属性:用于为关联对象的列指定前缀,以避免与其他表的列名冲突。fetchType
属性:用于指定加载方式,lazy
表示懒加载,eager
表示立即加载。select
属性:用于指定关联关系中需要调用的查询方法,这里可以指定一个 Mapper 方法(例如com.example.mapper.AddressMapper.getAddressById
),以实现更灵活的关联查询。
-
<collection>
标签:用于映射 User 和 Order 之间的一对多关系,将 Order 的列表映射到 User 对象中的orders
属性上。columnPrefix
属性:与<association>
中的columnPrefix
类似,用于避免列名冲突。fetchType
属性:指定加载方式,支持懒加载和立即加载。
常用的高级属性与用法
除了上面提到的 <association>
和 <collection>
标签中使用的常见属性外,还有一些高级的标签和属性可以提高 MyBatis 的映射功能和灵活性:
-
resultMap
标签中的autoMapping
属性:autoMapping
:自动映射列到属性,true
表示自动进行映射,false
表示不自动映射,可以用于减少手动配置的工作量。
<resultMap id="userResultMap" type="com.example.entity.User" autoMapping="true"> ... </resultMap>
-
<constructor>
标签:<constructor>
标签用于在对象创建时通过构造函数传递参数。- 可以使用
<idArg>
和<arg>
标签来指定构造函数参数的映射关系。
<resultMap id="userResultMap" type="com.example.entity.User"> <constructor> <idArg column="user_id" javaType="Long"/> <arg column="name" javaType="String"/> <arg column="age" javaType="int"/> </constructor> ... </resultMap>
-
<discriminator>
标签:- 用于处理多态对象的映射关系。可以根据某个字段的值来动态选择不同的子类映射。
- 例如根据用户类型字段
user_type
来区分普通用户和管理员。
<resultMap id="userResultMap" type="com.example.entity.User"> <id property="id" column="user_id"/> <result property="name" column="name"/> <discriminator javaType="String" column="user_type"> <case value="ADMIN" resultType="com.example.entity.AdminUser"/> <case value="USER" resultType="com.example.entity.RegularUser"/> </discriminator> </resultMap>
-
<collection>
和<association>
的cascade
属性:cascade
:指定在父对象操作时是否对关联对象进行级联操作,例如插入、更新和删除。MyBatis 不直接提供cascade
的配置,但可以通过编程方式实现类似的级联操作。
-
select
属性的动态关联:select
属性 可以使用动态参数,灵活地调用其他查询,特别是在需要根据不同条件关联查询的时候,能极大增强查询能力。
Mapper 接口
定义 UserMapper
接口用于数据库访问:
public interface UserMapper {
User getUserById(Long id);
}
定义 AddressMapper
接口,用于单独查询 Address 数据:
public interface AddressMapper {
Address getAddressById(Long id);
}
Service 层
在 Service 层通过注入 UserMapper
来调用查询方法:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.getUserById(id);
}
}
Controller 层
在 Controller 层处理前端的请求,返回用户数据:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
}
前端返回的 JSON 数据
假设用户的 ID 为 1,数据库中的数据如下:
User
表中:{id: 1, name: "John", age: 30, address_id: 1}
Address
表中:{id: 1, city: "New York", street: "5th Avenue"}
Orders
表中有两条记录:{id: 1, user_id: 1, product_name: "Laptop", price: 1200.00}
{id: 2, user_id: 1, product_name: "Smartphone", price: 800.00}
返回的 JSON 数据将会是:
{
"id": 1,
"name": "John",
"age": 30,
"address": {
"id": 1,
"city": "New York",
"street": "5th Avenue"
},
"orders": [
{
"id": 1,
"productName": "Laptop",
"price": 1200.00
},
{
"id": 2,
"productName": "Smartphone",
"price": 800.00
}
]
}
总结
<association>
标签 用于一对一映射,将Address
映射到User
对象的address
属性中,支持属性如columnPrefix
、fetchType
和select
来控制列前缀、加载方式和关联查询。<collection>
标签 用于一对多映射,将Order
列表映射到User
对象的orders
属性中,类似地支持columnPrefix
和fetchType
等属性。<constructor>
标签 可以用于通过构造函数创建对象。<discriminator>
标签 适用于多态映射,根据某些字段动态选择子类映射。
这些高级标签和属性在 MyBatis 的复杂查询和结果集映射中非常有用,能有效地将关系型数据库的数据转换为 Java 对象,从而简化开发工作。