在MyBatis中,
<association>
和<collection>
是两个重要的元素,它们用于处理对象之间的关联关系,特别是在处理一对多(如订单和订单项)和多对一(如用户和部门)等关系时。以下是这两个元素的简要总结:
<association>
<association>
元素用于处理“一对一”和“多对一”的关系。它用于将另一个表(或查询)的结果映射到一个复杂类型的属性上。
属性说明:
property
:指定映射到哪个Java对象的属性。javaType
:指定该属性的Java类型。column
:指定数据库中的哪一列用于作为外键。select
:指定一个用于加载关联对象的子查询ID。当使用懒加载时,这个属性特别有用。fetchType
:可选,指定加载策略(默认为eager,即预加载;也可以设置为lazy,即懒加载)。
示例:
<resultMap id="userResultMap" type="User">
<id property="id" column="id" />
<result property="username" column="username" />
<association property="department" javaType="Department" column="department_id" select="getDepartmentById" />
</resultMap>
在这个例子中,User
对象有一个Department
类型的属性,该属性通过department_id
与departments
表关联。当查询User
对象时,如果设置了select
属性,MyBatis会在需要时执行getDepartmentById
查询来加载关联的Department
对象。
<collection>
<collection>
元素用于处理“一对多”的关系。它用于将多个结果行映射到一个集合或数组上。
属性说明:
property
:指定映射到哪个Java对象的属性(通常是一个集合或数组)。ofType
:指定集合或数组中元素的类型。column
:指定数据库中的哪一列用于作为外键。foreignColumn
:指定关联表中哪一列是外键。select
:指定一个用于加载关联对象的子查询ID(同样,在懒加载时有用)。fetchType
:可选,指定加载策略(默认为eager,即预加载;也可以设置为lazy,即懒加载)。
示例:
<resultMap id="orderResultMap" type="Order">
<id property="id" column="id" />
<result property="orderDate" column="order_date" />
<collection property="orderItems" ofType="OrderItem" column="id" foreignColumn="order_id" select="getOrderItemsByOrderId" />
</resultMap>
在这个例子中,Order
对象有一个orderItems
集合,它包含了与当前订单关联的多个OrderItem
对象。当查询Order
对象时,如果设置了select
属性,MyBatis会在需要时执行getOrderItemsByOrderId
查询来加载关联的OrderItem
集合。
Springboot+mybatis详细代码实现
给出两个完整的示例:一个是使用<association>
元素的一对一关系示例,另一个是使用<collection>
元素的一对多关系示例。这两个示例都将基于Spring Boot + MyBatis框架。
示例环境准备
为了简化示例,我们假设已经搭建好了Spring Boot项目,并且已经配置好了MyBatis和数据库连接。
1. 实体类定义
首先定义几个简单的实体类:
User
实体类Department
实体类Order
实体类OrderItem
实体类
// User.java
public class User {
private Integer id;
private String username;
private Department department;
// Getters and Setters
}
// Department.java
public class Department {
private Integer id;
private String name;
// Getters and Setters
}
// Order.java
public class Order {
private Integer id;
private Date orderDate;
private List<OrderItem> orderItems = new ArrayList<>();
// Getters and Setters
}
// OrderItem.java
public class OrderItem {
private Integer id;
private Integer orderId;
private String productName;
private Integer quantity;
// Getters and Setters
}
2. 映射接口定义
定义MyBatis的映射接口:
// UserMapper.java
@Mapper
public interface UserMapper {
User getUserWithDepartment(Integer id);
}
// DepartmentMapper.java
@Mapper
public interface DepartmentMapper {
Department getDepartmentById(Integer id);
}
// OrderMapper.java
@Mapper
public interface OrderMapper {
Order getOrderWithOrderItems(Integer id);
}
// OrderItemMapper.java
@Mapper
public interface OrderItemMapper {
List<OrderItem> getOrderItemsByOrderId(Integer orderId);
}
3. 映射文件定义
接下来定义对应的XML映射文件:
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="userResultMap" type="com.example.entity.User">
<id property="id" column="id" />
<result property="username" column="username" />
<association property="department" javaType="com.example.entity.Department"
column="department_id" select="com.example.mapper.DepartmentMapper.getDepartmentById" />
</resultMap>
<select id="getUserWithDepartment" resultMap="userResultMap">
SELECT u.*, d.id AS department_id
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
WHERE u.id = #{id}
</select>
</mapper>
<!-- DepartmentMapper.xml -->
<mapper namespace="com.example.mapper.DepartmentMapper">
<resultMap id="departmentResultMap" type="com.example.entity.Department">
<id property="id" column="id" />
<result property="name" column="name" />
</resultMap>
<select id="getDepartmentById" resultMap="departmentResultMap">
SELECT * FROM departments WHERE id = #{id}
</select>
</mapper>
<!-- OrderMapper.xml -->
<mapper namespace="com.example.mapper.OrderMapper">
<resultMap id="orderResultMap" type="com.example.entity.Order">
<id property="id" column="id" />
<result property="orderDate" column="order_date" />
<collection property="orderItems" ofType="com.example.entity.OrderItem"
column="id" select="com.example.mapper.OrderItemMapper.getOrderItemsByOrderId" />
</resultMap>
<select id="getOrderWithOrderItems" resultMap="orderResultMap">
SELECT o.*, oi.order_id
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE o.id = #{id}
</select>
</mapper>
<!-- OrderItemMapper.xml -->
<mapper namespace="com.example.mapper.OrderItemMapper">
<resultMap id="orderItemResultMap" type="com.example.entity.OrderItem">
<id property="id" column="id" />
<result property="orderId" column="order_id" />
<result property="productName" column="product_name" />
<result property="quantity" column="quantity" />
</resultMap>
<select id="getOrderItemsByOrderId" resultMap="orderItemResultMap">
SELECT * FROM order_items WHERE order_id = #{id}
</select>
</mapper>
示例代码
接下来,我们将使用Spring Boot Controller来演示如何使用上述定义的映射器。
4. 控制器定义
创建一个控制器来获取数据:
@RestController
@RequestMapping("/api")
public class DataController {
@Autowired
private UserMapper userMapper;
@Autowired
private OrderMapper orderMapper;
@GetMapping("/user/{id}")
public User getUser(@PathVariable Integer id) {
return userMapper.getUserWithDepartment(id);
}
@GetMapping("/order/{id}")
public Order getOrder(@PathVariable Integer id) {
return orderMapper.getOrderWithOrderItems(id);
}
}
这样我们就完成了两个示例的完整代码实现。用户可以通过/api/user/{id}
和/api/order/{id}
来获取带有关联对象的数据。
请注意,这里的代码是简化的版本,实际项目中可能还需要考虑更多的细节,如异常处理、日志记录等。此外,数据库表结构也需要相应地定义好,以便映射关系能够正常工作。
注意点:
- 性能:懒加载可以减少初始查询的复杂性,但可能会增加后续访问关联对象时的数据库查询次数。预加载(默认行为)可以减少数据库查询次数,但可能会增加初始查询的复杂性和结果集的大小。
- 事务:懒加载可能导致跨多个事务边界的问题,特别是当在不同的会话或事务中访问关联对象时。预加载通常更适合在单个事务中处理所有数据。
- N+1查询问题:懒加载可能会导致N+1查询问题,即首先执行一个查询来获取N个主对象,然后对每个主对象执行一个额外的查询来获取其关联对象。这可以通过适当的查询优化或预加载来避免。
- 配置:确保正确配置MyBatis的懒加载和预加载策略,以避免不必要的性能问题或数据不一致性。