MyBatis的关联映射
1、关联关系概述
1)、实际的开发中,对数据库的操作常常会涉及到多张表,这在面向对象中就涉及到了对象与对象之间的关联关系。针对多表之间的操作,MyBatis提供了关联映射,通过关联映射就可以很好的处理对象与对象之间的关联关系。
2)、在关系型数据库中,多表之间存在着三种关联关系,分别为一对一、一对多和多对多,如下图所示:
a)、一对一:在任意一方引入对方主键作为外键;
b)、一对多:在“多”的一方,添加“一”的一方的主键作为外键;
c)、多对多:产生中间关系表,引入两张表的主键作为外键,两个主键成为联合主键或使用新的字段作为主键。
3)、在Java中,通过对象也可以进行关联关系描述,如图下图所示:
a)、一对一:在本类中定义对方类型的对象,如A类中定义B类类型的属性b,B类中定义A类类型的属性a;
b)、一对多:一个A类类型对应多个B类类型的情况,需要在A类中以集合的方式引入B类类型的对象,在B类中定义A类类型的属性a;
c)、多对多:在A类中定义B类类型的集合,在B类中定义A类类型的集合。
2、一对一
1)、元素中,包含了一个子元素,MyBatis就是通过该元素来处理一对一关联关系的。
2)、在元素中,通常可以配置以下属性:
a)、property:指定映射到的实体类对象中的属性,与表字段一一对应。
b)、column:指定数据库表中对应的字段。
c)、javaType:指定映射到实体对象属性的类型。
d)、select:指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询。
e)、fetchType:指定在关联查询时是否启用延迟加载。该属性有lazy和eager两个属性值,默认值为lazy(即默认关联映射延迟加载)。
3)、MyBatis加载关联关系对象主要通过两种方式:嵌套查询和嵌套结果。
4)、疑惑:虽然使用嵌套查询的方式比较简单,但是嵌套查询的方式要执行多条SQL语句,这对于大型数据集合和列表展示不是很好,因为这样可能会导致成百上千条关联的SQL语句被执行,从而极大的消耗数据库性能并且会降低查询效率。
5)、解决办法:MyBatis延迟加载的配置。使用MyBatis的延迟加载在一定程度上可以降低运行消耗并提高查询效率。MyBatis默认没有开启延迟加载,需要在核心配置文件中的元素内进行配置,具体配置方式如下:
6)、在映射文件中,元素和元素中都已默认配置了延迟加载属性,即默认属性fetchType="lazy"(属性fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无需在映射文件中再做配置。
7)、使用元素进行一对一关联映射非常简单,只需要参考如下两种示例配置即可。
8)、创建数据库表---tb_idcard和tb_person。
1 CREATE TABLEtb_idcard(2 id INT PRIMARY KEYAUTO_INCREMENT,3 CODE VARCHAR(18)4 );5
6 INSERT INTO tb_idcard(CODE) VALUES('13341631331213');7 INSERT INTO tb_idcard(CODE) VALUES('21311211321111');8
9 CREATE TABLEtb_person(10 id INT PRIMARY KEYAUTO_INCREMENT,11 NAME VARCHAR(32),12 age INT,13 sex VARCHAR(8),14 card_id INT UNIQUE,15 FOREIGN KEY(card_id) REFERENCEStb_idcard(id)16 );17
18 INSERT INTO tb_person(NAME, age, sex, card_id) VALUES('Rose', 29, '女', 1);19 INSERT INTO tb_person(NAME, age, sex, card_id) VALUES('tom', 27, '男', 2);
执行SQL查询语句如图所示:
9)、本章文件结构如下:
①证件持久化类:src/com/itheima/po/IdCard.java
1 packagecom.itheima.po;2 /**
3 * 证件持久化类4 */
5 public classIdCard {6 privateInteger id;7 privateString code;8
9 publicInteger getId() {10 returnid;11 }12
13 public voidsetId(Integer id) {14 this.id =id;15 }16
17 publicString getCode() {18 returncode;19 }20
21 public voidsetCode(String code) {22 this.code =code;23 }24
25 @Override26 publicString toString() {27 return "IdCard [id=" + id + ", code=" + code + "]";28 }29 }
②个人持久化类:src/com/itheima/po/Person.java
1 packagecom.itheima.po;2 /**
3 * 个人持久化类4 */
5 public classPerson {6 privateInteger id;7 privateString name;8 privateInteger age;9 privateString sex;10 private IdCard card; //个人关联的证件
11
12 publicInteger getId() {13 returnid;14 }15
16 public voidsetId(Integer id) {17 this.id =id;18 }19
20 publicString getName() {21 returnname;22 }23
24 public voidsetName(String name) {25 this.name =name;26 }27
28 publicInteger getAge() {29 returnage;30 }31
32 public voidsetAge(Integer age) {33 this.age =age;34 }35
36 publicString getSex() {37 returnsex;38 }39
40 public voidsetSex(String sex) {41 this.sex =sex;42 }43
44 publicIdCard getCard() {45 returncard;46 }47
48 public voidsetCard(IdCard card) {49 this.card =card;50 }51
52 @Override53 publicString toString() {54 return "Person [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + ", card=" + card + "]";55 }56 }
③创建证件映射文件:src/com/itheima/mapper/IdCardMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4
5
6
7
8
9 SELECT * from tb_idcard where id=#{id}10
11
④个人配置文件:src/com/itheima/mapper/PersonMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4
5
6
7
10
11 resultMap="IdCardWithPersonResultID">
12 SELECT * from tb_person where id=#{id}13
14
15
16
17
18
19
20
21
22
29
30 select="com.itheima.mapper.IdCardMapper.findCodeById" />
31
32
33
34
35 resultMap="IdCardWithPersonResult2ID">
36
37 SELECT p.*,idcard.code38 from tb_person p,tb_idcard idcard39 where p.card_id=idcard.id40 and p.id= #{id}41
42
43
44
45
46
47
48
49
50
53
54
55
56
57
58
59
⑤编写MyBatis的配置文件:src/mybatis-config.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 "http://mybatis.org/dtd/mybatis-3-config.dtd">
4
5
6
7
8
9
10
11
13
14
15
16
17
18
19
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
⑥一对一映射查询测试:src/com/itheima/test/MybatisAssociatedTest.java
1 packagecom.itheima.test;2 importorg.apache.ibatis.session.SqlSession;3 importorg.junit.Test;4
5 importcom.itheima.po.Orders;6 importcom.itheima.po.Person;7 importcom.itheima.po.User;8 importcom.itheima.utils.MybatisUtils;9 /**
10 * Mybatis关联查询映射测试类11 */
12 public classMybatisAssociatedTest {13 /**
14 * 嵌套查询15 */
16 @Test17 public voidfindPersonByIdTest() {18
19 //1、通过工具类生成SqlSession对象
20 SqlSession session =MybatisUtils.getSession();21
22 //2.使用MyBatis嵌套查询的方式查询id为1的人的信息
23 Person person = session.selectOne("com.itheima.mapper.PersonMapper.findPersonById", 1);24
25 //3、输出查询结果信息
26 System.out.println(person);27
28 //4、关闭SqlSession
29 session.close();30 }31
32 /**
33 * 嵌套结果34 */
35 @Test36 public voidfindPersonByIdTest2() {37
38 //1、通过工具类生成SqlSession对象
39 SqlSession session =MybatisUtils.getSession();40
41 //2.使用MyBatis嵌套结果的方法查询id为1的人的信息
42 Person person = session.selectOne("com.itheima.mapper.PersonMapper.findPersonById2", 1);43
44 //3、输出查询结果信息
45 System.out.println(person);46
47 //4、关闭SqlSession
48 session.close();49 }50 }
⑦嵌套查询如图所示:
⑧嵌套结果如图所示:
3、一对多
1)、开发人员接触更多的关联关系是一对多(或多对一)。例如,一个用户可以有多个订单,同时多个订单归一个用户所有。元素中,包含了一个子元素,MyBatis就是通过该元素来处理一对多关联关系的。
2)、子元素的属性大部分与元素相同,但其还包含一个特殊属性--ofType 。
3)、ofType属性与javaType属性对应,它用于指定实体对象中集合类属性所包含的元素类型。元素的使用也非常简单,同样可以参考如下两种示例进行配置,具体代码如下:
4)、创建数据库表tb_user和tb_orders:
1 CREATE TABLEtb_user(2 id INT(32) PRIMARY KEYAUTO_INCREMENT,3 username VARCHAR(32),4 address VARCHAR(256)5 );6 INSERT INTO tb_user VALUES('1', '张三', '深圳');7 INSERT INTO tb_user VALUES('2', '李四', '东莞');8
9 CREATE TABLEtb_orders(10 id INT(32) PRIMARY KEYAUTO_INCREMENT,11 number VARCHAR(32) NOT NULL, #订单号12 user_id INT(32) NOT NULL, #外键,该订单上客户id13 FOREIGN KEY(user_id) REFERENCEStb_user(id) # 定义外键引用14 );15 INSERT INTO tb_orders VALUES('2', '100111', '1');16 INSERT INTO tb_orders VALUES('3', '100112', '2');
执行SQL查询语句后如图所示:
①创建订单持久化类:src/com/itheima/po/Orders.java
1 packagecom.itheima.po;2
3 importjava.util.List;4
5 /**
6 * 订单持久化类7 */
8 public classOrders {9 private Integer id; //订单id
10 private String number; //订单编号
11
12
13 publicInteger getId() {14 returnid;15 }16
17 public voidsetId(Integer id) {18 this.id =id;19 }20
21 publicString getNumber() {22 returnnumber;23 }24
25 public voidsetNumber(String number) {26 this.number =number;27 }28
29 @Override30 publicString toString() {31 return "Orders [id=" + id + ", number=" + number + "]";32 }33
34 }
②创建用户持久化类:src/com/itheima/po/User.java
1 packagecom.itheima.po;2 importjava.util.List;3 /**
4 * 用户持久化类5 */
6 public classUser {7 private Integer id; //用户编号
8 private String username; //用户姓名
9 private String address; //用户地址
10 private List ordersList; //用户关联的订单,一对多:即一个用户可以有多个订单
11
12 publicInteger getId() {13 returnid;14 }15
16 public voidsetId(Integer id) {17 this.id =id;18 }19
20 publicString getUsername() {21 returnusername;22 }23
24 public voidsetUsername(String username) {25 this.username =username;26 }27
28 publicString getAddress() {29 returnaddress;30 }31
32 public voidsetAddress(String address) {33 this.address =address;34 }35
36 public ListgetOrdersList() {37 returnordersList;38 }39
40 public void setOrdersList(ListordersList) {41 this.ordersList =ordersList;42 }43
44 @Override45 publicString toString() {46 return "User [id=" + id + ", username=" + username + ", address=" + address + ", ordersList=" + ordersList + "]";47 }48 }
③创建用户实体映射文件:src/com/itheima/mapper/UserMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4
5
6
7
8
13
14 resultMap="UserWithOrdersResultID">
15 SELECT u.*, o.id as orders_id, o.number16 from tb_user u,tb_orders o17 WHERE u.id=o.user_id and u.id=#{id}18
19
20
21
22
23
24
25
26
32
33
36
37
38
39
40
④单元测试:
1 /**
2 * 一对多3 */
4 @Test5 public voidfindUserTest() {6
7 //1、通过工具类生成SqlSession对象
8 SqlSession session =MybatisUtils.getSession();9
10 //2、查询id为1的用户信息
11 User user = session.selectOne("com.itheima.mapper.UserMapper.findUserWithOrders", 1);12
13 //3、输出查询结果信息
14 System.out.println(user);15
16 //4、关闭SqlSession
17 session.close();18 }
⑤执行结果:
4、多对多
1)、在实际项目开发中,多对多的关联关系也是非常常见的。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单。
2)、在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表中的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。
3)、在MyBatis中,多对多的关联关系查询,同样可以使用前面介绍的元素进行处理(其用法和一对多关联关系查询语句用法基本相同)。
4)、创建数据库表tb_product和tb_ordersitem:
1 CREATE TABLEtb_product(2 id INT(32) PRIMARY KEYAUTO_INCREMENT,3 NAME VARCHAR(32),4 price DOUBLE
5 );6 INSERT INTO tb_product VALUES('1', 'JAVA入门基础', '96');7 INSERT INTO tb_product VALUES('2', 'JAVAWEB程序开发入门', '90');8 INSERT INTO tb_product VALUES('3', 'SSM框架整合实战', '90');9
10 CREATE TABLEtb_ordersitem( #创建中间表11 id INT(32) PRIMARY KEYAUTO_INCREMENT,12 orders_id INT(32),13 product_id INT(32),14 FOREIGN KEY(orders_id) REFERENCEStb_orders(id),15 FOREIGN KEY(product_id) REFERENCEStb_product(id)16 );17
18 INSERT INTO tb_ordersitem VALUES('1', '2', '1');19 INSERT INTO tb_ordersitem VALUES('2', '2', '3');20 INSERT INTO tb_ordersitem VALUES('3', '3', '3'); #注意引用值的存在
①创建商品持久化类:src/com/itheima/po/Product.java
1 packagecom.itheima.po;2 importjava.util.List;3 /**
4 * 商品持久化类5 */
6 public classProduct {7 private Integer id; //商品id
8 private String name; //商品名称
9 private Double price;//商品单价
10 private List orders; //与订单的关联属性
11
12 publicInteger getId() {13 returnid;14 }15
16 public voidsetId(Integer id) {17 this.id =id;18 }19
20 publicString getName() {21 returnname;22 }23
24 public voidsetName(String name) {25 this.name =name;26 }27
28 publicDouble getPrice() {29 returnprice;30 }31
32 public voidsetPrice(Double price) {33 this.price =price;34 }35
36 public ListgetOrders() {37 returnorders;38 }39
40 public void setOrders(Listorders) {41 this.orders =orders;42 }43
44 @Override45 publicString toString() {46 return "Product [id=" + id + ", name=" + name + ", price=" + price + "]";47 }48 }
②创建订单持久化类:src/com/itheima/po/Orders.java
1 packagecom.itheima.po;2
3 importjava.util.List;4
5 /**
6 * 订单持久化类7 */
8 public classOrders {9 private Integer id; //订单id
10 private String number; //订单编号11
12 //关联商品集合信息
13 private List productList; //一个订单可以包含多个商品,即一对多
14
15
16 publicInteger getId() {17 returnid;18 }19
20 public voidsetId(Integer id) {21 this.id =id;22 }23
24 publicString getNumber() {25 returnnumber;26 }27
28 public voidsetNumber(String number) {29 this.number =number;30 }31
32
33 public ListgetProductList() {34 returnproductList;35 }36
37 public void setProductList(ListproductList) {38 this.productList =productList;39 }40
41 @Override42 publicString toString() {43 return "Orders [id=" + id + ", number=" + number + ", productList=" + productList + "]";44 }45 }
③创建订单实体映射文件:src/com/itheima/mapper/OrdersMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4
5
6
7
8
9 resultMap="OrdersWithProductResultID">
10 select * from tb_orders WHERE id=#{id}11
12
13
14
15
16
17
18
23
24 select="com.itheima.mapper.ProductMapper.findProductById">
25
26
27
28
29
30 resultMap="OrdersWithPorductResult2ID">
31 select o.*,p.id as pid,p.name,p.price32 from tb_orders o,tb_product p,tb_ordersitem oi33 WHERE oi.orders_id=o.id34 and oi.product_id=p.id35 and o.id=#{id}36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
④创建商品映射文件:src/com/itheima/mapper/ProductMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4
5
6
7
8 resultType="Product">
9 SELECT * from tb_product where id IN (10 SELECT product_id FROM tb_ordersitem WHERE orders_id = #{id}11
14 )15
16
⑤多对多关联查询测试:
1 /**
2 * 多对多3 */
4 @Test5 public voidfindOrdersTest(){6
7 //1、通过工具类生成SqlSession对象
8 SqlSession session =MybatisUtils.getSession();9
10 //2、查询id为1的订单中的商品信息
11 Orders orders = session.selectOne("com.itheima.mapper.OrdersMapper.findOrdersWithPorduct", 1);12
13 //3、输出查询结果信息
14 System.out.println(orders);15
16 //4、关闭SqlSession
17 session.close();18 }
⑥执行结果:
个人总结:
Mybatis的关联映射很好地模拟了数据库多表查询,感觉很方便,初学还不太熟悉,应该多操作,多理解!