一对多
与一对一的关联关系相比,开发人员接触更多的关联关系是一对多(或多对一)。例如一个用户可以有多个订单,同时多个订单归一个用户所有。用户和订单的关联关系如图1所示。
图1 用户和订单的关联关系
那么使用MyBatis是怎么处理这种一对多关联关系的呢?在本书第7章所讲解的<resultMap>元素中,包含了一个<collection>子元素,MyBatis就是通过该元素来处理一对多关联关系的。<collection>子元素的属性大部分与<association>元素相同,但其还包含一个特殊属性——ofType。ofType属性与javaType属性对应,它用于指定实体对象中集合类属性所包含的元素类型。
<collection >元素的使用也非常简单,同样可以参考如下两种示例进行配置,具体代码如下:
<!--方式一:嵌套查询 -->
<collection property="ordersList" column="id"
ofType="com.itheima.po.Orders"
select=" com.itheima.mapper.OrdersMapper.selectOrders" />
<!--方式二:嵌套结果 -->
<collection property="ordersList" ofType="com.itheima.po.Orders">
<id property="id" column="orders_id" />
<result property="number" column="number" />
</collection>
在了解了MyBatis处理一对多关联关系的元素和方式后,接下来以用户和订单之间的这种一对多关联关系为例,详细讲解如何在MyBatis中处理一对多关联关系。具体步骤如下。
(1)在mybatis数据库中,创建两个数据表,分别为tb_user和tb_orders,同时在表中预先插入几条数据。执行的SQL语句如下所示。
# 创建一个名称为tb_user的表
CREATE TABLE tb_user (
id int(32) PRIMARY KEY AUTO_INCREMENT,
username varchar(32),
address varchar(256)
);
# 插入3条数据
INSERT INTO tb_user VALUES ('1', '詹姆斯', '克利夫兰');
INSERT INTO tb_user VALUES ('2', '科比', '洛杉矶');
INSERT INTO tb_user VALUES ('3', '保罗', '洛杉矶');
# 创建一个名称为tb_orders的表
CREATE TABLE tb_orders (
id int(32) PRIMARY KEY AUTO_INCREMENT,
number varchar(32) NOT NULL,
user_id int(32) NOT NULL,
FOREIGN KEY(user_id) REFERENCES tb_user(id)
);
# 插入3条数据
INSERT INTO tb_orders VALUES ('1', '1000011', '1');
INSERT INTO tb_orders VALUES ('2', '1000012', '1');
INSERT INTO tb_orders VALUES ('3', '1000013', '2');
完成上述操作后,数据库tb_user表和tb_orders表中的数据如图2所示。
图2 tb_user和tb_orders表
(2)在com.itheima.po包中,创建持久化类Orders和User,并在两个类中定义相关属性和方法,如文件1和文件2所示。
文件1 Orders.java
1 package com.itheima.po;
2 /**
3 * 订单持久化类
4 */
5 public class Orders {
6 private Integer id; //订单id
7 private String number;//订单编号
8 public Integer getId() {
9 return id;
10 }
11 public void setId(Integer id) {
12 this.id = id;
13 }
14 public String getNumber() {
15 return number;
16 }
17 public void setNumber(String number) {
18 this.number = number;
19 }
20 @Override
21 public String toString() {
22 return "Orders [id=" + id + ", number=" + number + "]";
23 }
24 }
文件2 User.java
1 package com.itheima.po;
2 import java.util.List;
3 /**
4 * 用户持久化类
5 */
6 public class User {
7 private Integer id; // 用户编号
8 private String username; // 用户姓名
9 private String address; // 用户地址
10 private List<Orders> ordersList; //用户关联的订单
11 public Integer getId() {
12 return id;
13 }
14 public void setId(Integer id) {
15 this.id = id;
16 }
17 public String getUsername() {
18 return username;
19 }
20 public void setUsername(String username) {
21 this.username = username;
22 }
23 public String getAddress() {
24 return address;
25 }
26 public void setAddress(String address) {
27 this.address = address;
28 }
29 public List<Orders> getOrdersList() {
30 return ordersList;
31 }
32 public void setOrdersList(List<Orders> ordersList) {
33 this.ordersList = ordersList;
34 }
35 @Override
36 public String toString() {
37 return "User [id=" + id + ", username=" + username + ", address="
38 + address + ", ordersList=" + ordersList + "]";
39 }
40 }
在上述两个文件中,分别定义了各自的属性以及对应的getter/setter方法,同时为了方便查看输出结果,还重写了toString()方法。
(3)在com.itheima.mapper包中,创建用户实体映射文件UserMapper.xml,并在文件中编写一对多关联映射查询的配置,如文件3所示。
文件3 UserMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4 <!-- namespace表示命名空间 -->
5 <mapper namespace="com.itheima.mapper.UserMapper">
6 <!-- 一对多:查看某一用户及其关联的订单信息
7 注意:当关联查询出的列名相同,则需要使用别名区分 -->
8 <select id="findUserWithOrders" parameterType="Integer"
9 resultMap="UserWithOrdersResult">
10 SELECT u.*,o.id as orders_id,o.number
11 from tb_user u,tb_orders o
12 WHERE u.id=o.user_id
13 and u.id=#{id}
14 </select>
15 <resultMap type="User" id="UserWithOrdersResult">
16 <id property="id" column="id"/>
17 <result property="username" column="username"/>
18 <result property="address" column="address"/>
19 <!-- 一对多关联映射:collection
20 ofType表示属性集合中元素的类型,List<Orders>属性即Orders类 -->
21 <collection property="ordersList" ofType="Orders">
22 <id property="id" column="orders_id"/>
23 <result property="number" column="number"/>
24 </collection>
25 </resultMap>
26 </mapper>
在文件3中,使用了MyBatis嵌套结果的方式定义了一个根据用户id查询用户及其关联的订单信息的select语句。因为返回的用户对象中,包含Orders集合对象属性,所以需要手动编写结果映射信息。
(4)将映射文件UserMapper.xml的路径配置到核心配置文件mybatis-config.xml中,其代码如下所示。
<mapper resource="com/itheima/mapper/UserMapper.xml" />
(5)在测试类MybatisAssociatedTest中,编写测试方法findUserTest(),其代码如下所示。
/**
* 一对多
*/
@Test
public void findUserTest() {
// 1、通过工具类生成SqlSession对象
SqlSession session = MybatisUtils.getSession();
// 2、查询id为1的用户信息
User user = session.selectOne("com.itheima.mapper."
+ "UserMapper.findUserWithOrders", 1);
// 3、输出查询结果信息
System.out.println(user);
// 4、关闭SqlSession
session.close();
}
使用JUnit4执行findUserTest()方法后,控制台输出结果如图3所示。
图3 运行结果
从图3可以看出,使用MyBatis嵌套结果的方式查询出了用户及其关联的订单集合信息。这就是MyBatis一对多的关联查询。
需要注意的是,上述案例从用户的角度出发,用户与订单之间是一对多的关联关系,但如果从单个订单的角度出发,一个订单只能属于一个用户,即一对一的关联关系。读者可根据9.2小节内容实现单个订单与用户之间的一对一关联关系,由于篇幅有限,这里不再重复赘述。