MyBatis系统学习(四)——MyBatis的关联映射和缓存机制
MyBatis 是一个优秀的持久层框架,它通过 XML 或注解将 Java 对象与 SQL 语句相映射,简化了 JDBC 代码,增强了 SQL 的灵活性。在复杂业务场景中,数据库表之间经常存在一对一、一对多、多对多的关联关系,MyBatis 提供了相应的机制来处理这些映射问题。此外,MyBatis 的缓存机制可以有效提高数据访问效率。
接下来,我将详细介绍 MyBatis 中的关联映射和缓存机制。
一、MyBatis 关联映射概述
在关系型数据库中,表与表之间的关联关系常见的有:
- 一对一(One-to-One):两个表中一条记录对应另一个表中的一条记录。
- 一对多(One-to-Many):一个表中一条记录对应另一个表中的多条记录。
- 多对多(Many-to-Many):两个表中的多条记录彼此关联。
MyBatis 提供了以下两种主要方式来实现关联映射:
- 使用
resultMap
标签进行复杂的对象关系映射。 - 通过注解来实现简单的映射。
二、一对一查询
一对一关系通常体现在数据库中表与表之间通过外键关联。MyBatis 支持通过 resultMap
标签来定义一对一的关联关系。
实现步骤:
- 在数据库中,两个表具有外键关联。例如,
User
表和Address
表,User
表有一个address_id
外键指向Address
表。 - 在 MyBatis 的配置文件中,通过
resultMap
定义关联关系。
示例:
假设我们有 User
和 Address
表,User
表中有一个 address_id
作为外键关联 Address
表。
<!-- 一对一映射 resultMap -->
<resultMap id="userResultMap" type="com.example.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="address" javaType="com.example.Address">
<id property="id" column="address_id"/>
<result property="city" column="city"/>
<result property="street" column="street"/>
</association>
</resultMap>
<select id="selectUserWithAddress" resultMap="userResultMap">
SELECT u.id, u.name, a.id AS address_id, a.city, a.street
FROM User u
LEFT JOIN Address a ON u.address_id = a.id
WHERE u.id = #{id}
</select>
这里,<association>
用于描述一对一的关系。User
对象的 address
属性将与 Address
对象对应。
三、一对多查询
一对多关系通常表示一个表中的一条记录可以对应另一个表中的多条记录。MyBatis 可以通过 collection
标签来处理一对多的关系。
示例:
假设 User
表和 Order
表存在一对多的关系,一个用户可以有多个订单。
<!-- 一对多映射 resultMap -->
<resultMap id="userWithOrdersResultMap" type="com.example.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="orders" ofType="com.example.Order">
<id property="id" column="order_id"/>
<result property="orderNumber" column="order_number"/>
<result property="amount" column="amount"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrdersResultMap">
SELECT u.id, u.name, o.id AS order_id, o.order_number, o.amount
FROM User u
LEFT JOIN Orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
这里使用 <collection>
标签定义了 User
对象中包含的 orders
集合。通过这个映射,MyBatis 会自动将查询到的 Order
记录映射到 User
对象的 orders
属性中。
四、多对多查询
多对多的关系通常通过中间表来表示,MyBatis 也可以处理这样的复杂关联。
示例:
假设 Student
表和 Course
表通过 Student_Course
中间表存在多对多关系。
<!-- 多对多映射 resultMap -->
<resultMap id="studentWithCoursesResultMap" type="com.example.Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="courses" ofType="com.example.Course">
<id property="id" column="course_id"/>
<result property="courseName" column="course_name"/>
</collection>
</resultMap>
<select id="selectStudentWithCourses" resultMap="studentWithCoursesResultMap">
SELECT s.id, s.name, c.id AS course_id, c.course_name
FROM Student s
LEFT JOIN Student_Course sc ON s.id = sc.student_id
LEFT JOIN Course c ON sc.course_id = c.id
WHERE s.id = #{id}
</select>
这里使用 <collection>
定义了 Student
对象中包含的 courses
集合。查询结果会自动映射到 Student
对象的 courses
属性中。
五、MyBatis 缓存机制
MyBatis 提供了两级缓存机制:
- 一级缓存(Local Cache):基于
SqlSession
的缓存。一级缓存默认开启,且作用范围是SqlSession
级别的。在同一个SqlSession
中执行相同的 SQL 语句,MyBatis 会从缓存中取数据而不是再去查询数据库。 - 二级缓存(Global Cache):基于
mapper
映射文件的缓存。作用范围是mapper
映射文件级别,即同一个mapper
中的多个SqlSession
都可以共享该缓存。二级缓存需要手动配置开启。
一级缓存:
- 默认情况下一级缓存是开启的。
- 一级缓存的生效条件:同一个
SqlSession
、相同的查询条件。
示例:
SqlSession session = sqlSessionFactory.openSession();
User user1 = session.selectOne("selectUser", 1); // 从数据库查询
User user2 = session.selectOne("selectUser", 1); // 从缓存中查询
二级缓存:
二级缓存需要在 mapper
映射文件中手动开启,且 Java 类必须实现序列化接口。
配置方式:
-
在 MyBatis 配置文件中开启二级缓存:
<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在
mapper
文件中开启二级缓存:<cache/>
-
Java 对象必须实现
Serializable
接口,确保对象能被序列化并存入缓存。
二级缓存失效的条件:
- 执行了增删改操作,相关缓存会失效。
- 不同的
SqlSession
。
六、MyBatis 相关常用知识点总结
- 动态 SQL:MyBatis 提供了
if
、choose
、when
、foreach
等标签来处理动态 SQL,满足多变的业务需求。 resultMap
的使用:resultMap
是 MyBatis 最强大的功能之一,它允许灵活地将查询结果映射到复杂的 Java 对象中。- 多数据源支持:MyBatis 支持多数据源的配置,可以方便地在项目中切换或同时操作多个数据库。
- 分页插件:MyBatis 本身不提供分页功能,但是可以通过插件(如
PageHelper
)来实现数据库层面的分页操作。
七、综合案例
假设我们有三个表:User
表、Order
表和 Product
表。User
表与 Order
表是一对多的关系,Order
表与 Product
表是多对多的关系(通过 Order_Product
中间表实现)。我们希望查询用户、用户的订单以及每个订单中的产品信息。
数据库表设计:
User
表:id
、name
。Order
表:id
、order_number
、user_id
。Product
表:id
、product_name
。Order_Product
表:order_id
、product_id
。
MyBatis 配置文件:
<!-- 综合案例 resultMap -->
<resultMap id="userWithOrdersAndProductsResultMap" type="com.example.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="orders" ofType="com.example.Order">
<id property="id" column="order_id"/>
<result property="orderNumber" column="order_number"/>
<collection property="products" ofType="com.example.Product">
<id property
="id" column="product_id"/>
<result property="productName" column="product_name"/>
</collection>
</collection>
</resultMap>
<select id="selectUserWithOrdersAndProducts" resultMap="userWithOrdersAndProductsResultMap">
SELECT u.id, u.name, o.id AS order_id, o.order_number, p.id AS product_id, p.product_name
FROM User u
LEFT JOIN Orders o ON u.id = o.user_id
LEFT JOIN Order_Product op ON o.id = op.order_id
LEFT JOIN Product p ON op.product_id = p.id
WHERE u.id = #{id}
</select>
Java 类设计:
public class User implements Serializable {
private Integer id;
private String name;
private List<Order> orders;
// getters and setters
}
public class Order implements Serializable {
private Integer id;
private String orderNumber;
private List<Product> products;
// getters and setters
}
public class Product implements Serializable {
private Integer id;
private String productName;
// getters and setters
}
通过以上配置和映射,当执行 selectUserWithOrdersAndProducts
查询时,MyBatis 会将用户、订单和订单中的产品信息自动映射到 User
对象及其关联的 Order
和 Product
对象中。
结语
MyBatis 在处理复杂的对象关系映射时提供了极大的灵活性。通过 resultMap
和缓存机制,我们能够有效地管理查询结果并提升系统性能。在实际开发中,掌握 MyBatis 的这些功能,可以更轻松地应对持久层的复杂逻辑。