第9章 MyBatis的关系映射
在实际开发中,对数据库的操作通常涉及多张表,涉及了对象和对象之间的关联关系。针对多表之间的操作,MyBatis提供了关联映射,通过关联映射就可以很好的处理对象与对象之间的关联关系
9.1 关联关系概述
一对一:在任意一方引入对方主键作为外键。
一对多:在“多”的一方,添加“一“的一方的主键作为外键。(连着多条线的一方是“多”)
多对多:产生中间关系表,引入两张表的主键作为外键,两个主键成为联合主键或使用新的字段作为主键。
用Java对象描述
class A{ B b; } class B{ A a; } //一对一:在本类中定义对方类型的对象,如A类中定义B类类型的属性b,B类中定义A类类型的属性a
class A{ List<B>b; } class B{ A a; } //一对多:就是一个A类类型中对应多个B类类型的情况,需要在A类中以集合的方式引入B类类型的对象,在B类中定义A类类型的属性a
class A{ List<B> b; } class B{ List<A> a; } //多对多:在A类中定义B类类型的集合,在B类中定义A类类型的集合
一对一:用<association>元素处理
a)、property:指定映射到的实体类对象中的属性,与表字段一一对应。
b)、column:指定数据库表中对应的字段。
c)、javaType:指定映射到实体对象属性的类型。
d)、select:指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询。
e)、fetchType:指定在关联查询时是否启用延迟加载。该属性有lazy和eager两个属性值,默认值为lazy(即默认关联映射延迟加载)。
MyBatis加载关联关系对象主要通过两种方式:嵌套查询和嵌套结果。
问题:虽然使用嵌套查询的方式比较简单,但是嵌套查询的方式要执行多条SQL语句,这对于大型数据集合和列表展示不是很好,因为这样可能会导致成百上千条关联的SQL语句被执行,从而极大的消耗数据库性能并且会降低查询效率。类似暴力for循环吧。
解决:MyBatis延迟加载的配置。使用MyBatis的延迟加载在一定程度上可以降低运行消耗并提高查询效率。MyBatis默认没有开启延迟加载,需要在核心配置文件中的<settings>元素内进行配置,具体配置方式如下:
<settings> <setting name="lazyLoadingEnabled" value="true" /> <setting name="aggressiveLazyLoading" value="false"/> </settings>
在映射文件中,<association>元素和<collection>元素中都已默认配置了延迟加载属性,即默认属性fetchType="lazy"(属性fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无需在映射文件中再做配置。
举例:以个人和身份证之间的一对一关联关系
创建两个表tb_idcard和tb_person
USE mybatis; CREATE TABLE tb_idcard( id INT PRIMARY KEY AUTO_INCREMENT, CODE VARCHAR(18) ); INSERT INTO tb_idcard(CODE) VALUE('152221198711020624'); INSERT INTO tb_idcard(CODE) VALUE('152201199008150317'); CREATE TABLE tb_person( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(32), age INT, sex VARCHAR(8), card_id INT UNIQUE, FOREIGN KEY(card_id) REFERENCES tb_idcard(id) ); INSERT INTO tb_person(NAME,age,sex,card_id) VALUE('Rose',29,'女',1); INSERT INTO tb_person(NAME,age,sex,card_id) VALUE('tom',27,'男',2);
此时表内数据:
创建持久化类IdCard和Person
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.itheima.po; /** * 证件持久化类 */ public class IdCard { private Integer id; private String code; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Override public String toString() { return "IdCard [id=" + id + ", code=" + code + "]"; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.itheima.po; /** * 个人持久化类 */ public class Person { private Integer id; private String name; private Integer age; private String sex; private IdCard card; //个人关联的证件 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public IdCard getCard() { return card; } public void setCard(IdCard card) { this.card = card; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", " + "age=" + age + ", sex=" + sex + ", card=" + card + "]"; } }
嵌套查询:
IdCardMapper.xml映射文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.IdCardMapper"> <!-- 根据id查询证件信息,最普通的配置信息 --> <select id="findCodeById" parameterType="Integer" resultType="IdCard"> SELECT * from tb_idcard where id=#{id} </select> </mapper>
PersonMapper.xml映射文件:
<!-- 嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 --> <select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult123"> SELECT * from tb_person where id=#{id} </select> <!-- resultMap最终还是要将结果映射到pojo上,type就是指定映射到哪一个pojo --> <resultMap type="Person" id="IdCardWithPersonResult123"> <id property="id" column="id" /> <result property="name" column="name" /> <result property="age" column="age" /> <result property="sex" column="sex" /> <!-- 一对一:association使用select属性引入另外一条SQL语句,是另一个映射文件的select元素id --> <association property="card" column="card_id" javaType="IdCard" select="com.itheima.mapper.IdCardMapper.findCodeById" /> </resultMap>
测试方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 嵌套查询 */ @Test public void findPersonByIdTest() { // 1、通过工具类生成SqlSession对象 SqlSession session = MybatisUtils.getSession(); // 2.使用MyBatis嵌套查询的方式查询id为1的人的信息 Person person = session.selectOne("com.itheima.mapper." + "PersonMapper.findPersonById", 1); // 3、输出查询结果信息 System.out.println(person); // 4、关闭SqlSession session.close(); }
运行结果:执行了多条简单的SQL语句
嵌套结果:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!-- 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集 --> <select id="findPersonById2" parameterType="Integer" resultMap="IdCardWithPersonResult2"> SELECT p.*,idcard.code from tb_person p,tb_idcard idcard where p.card_id=idcard.id and p.id= #{id} </select> <resultMap type="Person" id="IdCardWithPersonResult2"> <id property="id" column="id" /><!-- 声明主键,id是关联查询对象的唯一标识符 --> <result property="name" column="name" /> <result property="age" column="age" /> <result property="sex" column="sex" /> <association property="card" javaType="IdCard"> <id property="id" column="card_id" /> <result property="code" column="code" /> </association> </resultMap>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 嵌套结果 */ @Test public void findPersonByIdTest2() { // 1、通过工具类生成SqlSession对象 SqlSession session = MybatisUtils.getSession(); // 2.使用MyBatis嵌套结果的方法查询id为1的人的信息 Person person = session.selectOne("com.itheima.mapper." + "PersonMapper.findPersonById2", 1); // 3、输出查询结果信息 System.out.println(person); // 4、关闭SqlSession session.close(); }
测试结果:只执行了一条复杂的SQL语句。
DEBUG [main] - ==> Preparing: SELECT p.*,idcard.code from tb_person p,tb_idcard idcard where p.card_id=idcard.id and p.id= ?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
Person [id=1, name=Rose, age=29, sex=女, card=IdCard [id=1, code=152221198711020624]]
select p.*,idcard.code from tb_person p,tb_idcard idcard where p.card_id=idcard.id and p.id = #{id}
这里的p是tb_person的别名,因为别名容易写,第二个tb_person后接空格再加p是格式,如果不用别名就是:
select tb_person.*,tb_idcard.code from tb_person,tb_idcard where tb_person.card_id=tb_idcard.id and tb_person.id = #{id}
一对一关系配置模板:
9.3 一对多
<resultMap>元素中,包含一个子元素<collection>元素,属性大部分和<association>元素相同,但有一个特殊属性ofType,这个属性和javaType属性对应,用于指定实体对象中集合类属性所包含的元素类型。
<collection>元素的使用模板:
1.在MySQL中建表tb_user和tb_orders,并插入几条数据
USE mybatis; CREATE TABLE tb_user( id INT(32) PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32), address VARCHAR(256) ); INSERT INTO tb_user VALUES('1','詹姆斯','克利夫兰'); INSERT INTO tb_user VALUES('2','科比','洛杉矶'); INSERT INTO tb_user VALUES('3','保罗','洛杉矶'); USER mybatis; 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) ); INSERT INTO tb_orders VALUES('1','1000011','1'); INSERT INTO tb_orders VALUES('2','1000012','1'); INSERT INTO tb_orders VALUES('3','1000013','2');
结果:
2.创建持久化类Orders和User
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.itheima.po; import java.util.List; /** * 用户持久化类 */ public class User { private Integer id; // 用户编号 private String username; // 用户姓名 private String address; // 用户地址 private List<Orders> ordersList; //用户关联的订单................................... public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public List<Orders> getOrdersList() { return ordersList; } public void setOrdersList(List<Orders> ordersList) { this.ordersList = ordersList; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", address=" + address + ", ordersList=" + ordersList + "]"; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.itheima.po; import java.util.List; /** * 订单持久化类 */ public class Orders { private Integer id; //订单id private String number;//订单编号 //关联商品集合信息 private List<Product> productList; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } // @Override // public String toString() { // return "Orders [id=" + id + ", number=" + number + "]"; // } public List<Product> getProductList() { return productList; } public void setProductList(List<Product> productList) { this.productList = productList; } @Override public String toString() { return "Orders [id=" + id + ", number=" + number + ", productList=" + productList + "]"; } }
3.创建映射文件UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace表示命名空间 --> <mapper namespace="com.itheima.mapper.UserMapper"> <!-- 一对多:查看某一用户及其关联的订单信息 注意:当关联查询出的列名相同,则需要使用别名区分 --> <select id="findUserWithOrders123" parameterType="Integer" resultMap="UserWithOrdersResult123"> SELECT u.*,o.id as orders_id,o.number from tb_user u,tb_orders o WHERE u.id=o.user_id and u.id=#{id} </select> <resultMap type="User" id="UserWithOrdersResult123"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="address" column="address"/> <!-- 一对多关联映射:collection ofType表示属性集合中元素的类型,List<Orders>属性即Orders类 --> <collection property="ordersList" ofType="Orders"> <id property="id" column="orders_id"/> <result property="number" column="number"/> </collection> </resultMap> </mapper>
4.测试方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 一对多 */ @Test public void findUserTest() { // 1、通过工具类生成SqlSession对象 SqlSession session = MybatisUtils.getSession(); // 2、查询id为1的用户信息 User user = session.selectOne("com.itheima.mapper." + "UserMapper.findUserWithOrders123", 1); // 3、输出查询结果信息 System.out.println(user); // 4、关闭SqlSession session.close(); }
5.查询结果:
DEBUG [main] - ==> Preparing: SELECT u.*,o.id as orders_id,o.number from tb_user u,tb_orders o WHERE u.id=o.user_id and u.id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 2
User [id=1, username=詹姆斯, address=克利夫兰, ordersList=[Orders [id=1, number=1000011, productList=null], Orders [id=2, number=1000012, productList=null]]]
9.4 多对多
以商品和订单为例,一个订单可以包含多个商品,一个商品可以属于多个订单,数据库中的多对多联系通常使用一个中间表来维护,中间表中的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。
(外键:这个表的某一属性是别的表的主键,可以有重复,可以有多个,可以为空)
1.建表:tb_product和tb_ordersitem
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
USE mybatis; CREATE TABLE tb_product( id INT(32) PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(32), price DOUBLE ); INSERT INTO tb_product VALUES('1','Java基础入门','44.5'); INSERT INTO tb_product VALUES('2','Java Web程序开发入门','38.5'); INSERT INTO tb_product VALUES('3','SSM框架整合实战','50'); CREATE TABLE tb_ordersitem( id INT(32) PRIMARY KEY AUTO_INCREMENT, orders_id INT(32), product_id INT(32), FOREIGN KEY(orders_id) REFERENCES tb_orders(id), FOREIGN KEY(product_id) REFERENCES tb_product(id) ); INSERT INTO tb_ordersitem VALUES('1','1','1'); INSERT INTO tb_ordersitem VALUES('2','1','3'); INSERT INTO tb_ordersitem VALUES('3','3','3');
建表后:
2.创建持久化类Product表示商品,订单用以前的Order
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.itheima.po; import java.util.List; /** * 商品持久化类 */ public class Product { private Integer id; //商品id private String name; //商品名称 private Double price;//商品单价 private List<Orders> orders; //与订单的关联属性........................... public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public List<Orders> getOrders() { return orders; } public void setOrders(List<Orders> orders) { this.orders = orders; } @Override public String toString() { return "Product [id=" + id + ", name=" + name + ", price=" + price + "]"; } }
3.创建订单实体映射文件OrdersMapper.xml和ProductMapper.xml
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.OrdersMapper"> <!-- 多对多嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 定义了一个id为findOrdersWithPorduct的select语句来查询订单及其关联的商品信息--> <select id="findOrdersWithPorduct" parameterType="Integer" resultMap="OrdersWithProductResult"> select * from tb_orders WHERE id=#{id} </select> <resultMap type="Orders" id="OrdersWithProductResult"> <id property="id" column="id" /> <result property="number" column="number" /> <!-- property属性表示订单持久化类中的商品属性,ofType属性表示集合中的数据为Product类型,而column的 属性值会作为参数执行ProductMapper中定义的id为findProductById的执行语句来查询订单中的商品信息--> <collection property="productList" column="id" ofType="Product" select="com.itheima.mapper.ProductMapper.findProductById"> </collection> </resultMap> <!-- 多对多嵌套结果查询:查询某订单及其关联的商品详情 --> <select id="findOrdersWithPorduct2" parameterType="Integer" resultMap="OrdersWithPorductResult2"> select o.*,p.id as pid,p.name,p.price from tb_orders o,tb_product p,tb_ordersitem oi WHERE oi.orders_id=o.id and oi.product_id=p.id and o.id=#{id} </select> <!-- 自定义手动映射类型 --> <resultMap type="Orders" id="OrdersWithPorductResult2"> <id property="id" column="id" /> <result property="number" column="number" /> <!-- 多对多关联映射:collection --> <collection property="productList" ofType="Product"> <id property="id" column="pid" /> <result property="name" column="name" /> <result property="price" column="price" /> </collection> </resultMap> </mapper>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.ProductMapper"> <select id="findProductById" parameterType="Integer" resultType="Product"> SELECT * from tb_product where id IN( SELECT product_id FROM tb_ordersitem WHERE orders_id = #{id} ) </select> </mapper>
4.测试方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 多对多 */ @Test public void findOrdersTest(){ // 1、通过工具类生成SqlSession对象 SqlSession session = MybatisUtils.getSession(); // 2、查询id为1的订单中的商品信息 Orders orders = session.selectOne("com.itheima.mapper." + "OrdersMapper.findOrdersWithPorduct", 1); // 3、输出查询结果信息 System.out.println(orders); // 4、关闭SqlSession session.close(); }
5.测试结果:
DEBUG [main] - ==> Preparing: select * from tb_orders WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
DEBUG [main] - ==> Preparing: SELECT * from tb_product where id IN( SELECT product_id FROM tb_ordersitem WHERE orders_id = ? )
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 2
Orders [id=1, number=1000011, productList=[Product [id=1, name=Java基础入门, price=44.5], Product [id=3, name=SSM框架整合实战, price=50.0]]]