Java EE学习笔记(九)
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)、<resultMap>元素中,包含了一个<association>子元素,MyBatis就是通过该元素来处理一对一关联关系的。
2)、在<association>元素中,通常可以配置以下属性:
a)、property:指定映射到的实体类对象中的属性,与表字段一一对应。
b)、column:指定数据库表中对应的字段。
c)、javaType:指定映射到实体对象属性的类型。
d)、select:指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询。
e)、fetchType:指定在关联查询时是否启用延迟加载。该属性有lazy和eager两个属性值,默认值为lazy(即默认关联映射延迟加载)。
3)、MyBatis加载关联关系对象主要通过两种方式:嵌套查询和嵌套结果。
4)、疑惑:虽然使用嵌套查询的方式比较简单,但是嵌套查询的方式要执行多条SQL语句,这对于大型数据集合和列表展示不是很好,因为这样可能会导致成百上千条关联的SQL语句被执行,从而极大的消耗数据库性能并且会降低查询效率。
5)、解决办法:MyBatis延迟加载的配置。使用MyBatis的延迟加载在一定程度上可以降低运行消耗并提高查询效率。MyBatis默认没有开启延迟加载,需要在核心配置文件中的<settings>元素内进行配置,具体配置方式如下:
<settings>
<setting name="lazyLoadingEnabled" value="true" />
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
6)、在映射文件中,<association>元素和<collection>元素中都已默认配置了延迟加载属性,即默认属性fetchType="lazy"(属性fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无需在映射文件中再做配置。
7)、使用<association>元素进行一对一关联映射非常简单,只需要参考如下两种示例配置即可。
<!--方式1:嵌套查询-->
<association property="card" column="card_id"
javaType="com.itheima.po.IdCard"
select="com.itheima.mapper.IdCardMapper.findCodeById" />
<!--方式2:嵌套结果-->
<association property="card" javaType="com.itheima.po.IdCard">
<id property="id" column="card_id" />
<result property="code" column="code" />
</association>
8)、创建数据库表---tb_idcard和tb_person。
CREATE TABLE tb_idcard(
id INT PRIMARY KEY AUTO_INCREMENT,
CODE VARCHAR(18)
); INSERT INTO tb_idcard(CODE) VALUES('');
INSERT INTO tb_idcard(CODE) VALUES(''); 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) VALUES('Rose', 29, '女', 1);
INSERT INTO tb_person(NAME, age, sex, card_id) VALUES('tom', 27, '男', 2);
执行SQL查询语句如图所示:
9)、本章文件结构如下:
①证件持久化类:src/com/itheima/po/IdCard.java
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 + "]";
}
}
②个人持久化类:src/com/itheima/po/Person.java
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 + "]";
}
}
③创建证件映射文件:src/com/itheima/mapper/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>
④个人配置文件:src/com/itheima/mapper/PersonMapper.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.PersonMapper"> <!-- 嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型
引用下面定义的resultMap的ID,作一个实体类属性和数据库列属性的映射关系
-->
<select id="findPersonById" parameterType="Integer"
resultMap="IdCardWithPersonResultID">
SELECT * from tb_person where id=#{id}
</select> <resultMap type="Person" id="IdCardWithPersonResultID">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="sex" column="sex" /> <!--除了返回个人对象的基本属性外还有一个关联的card属性,需要手动编写结果映射
一对一:<association>元素使用select属性引入另外一条SQL语句
javaType:映射到实体对象属性card的类型为IdCard
property:映射到的实体类对象属性card
嵌套查询的方法是先执行一个简单的SQL语句,然后进行结果映射时,
将关联对象在<association>元素中使用select属性执行另一条SQL语句(即在IdCardMapper.xml中的SQL)
-->
<association property="card" column="card_id" javaType="IdCard"
select="com.itheima.mapper.IdCardMapper.findCodeById" />
</resultMap> <!-- 嵌套结果:使用嵌套结果映射 来处理重复的联合结果的子集 -->
<select id="findPersonById2" parameterType="Integer"
resultMap="IdCardWithPersonResult2ID">
<!-- 编写一条复杂的多表关联的SQL查询语句 -->
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="IdCardWithPersonResult2ID"> <id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="sex" column="sex" /> <!-- 嵌套结果查询:
在<association>元素中继续使用相关子元素进行数据库表字段和实体类属性的一一映射,详细展开一一映射
-->
<association property="card" javaType="IdCard">
<id property="id" column="card_id" />
<result property="code" column="code" />
</association>
</resultMap> </mapper>
⑤编写MyBatis的配置文件:src/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 引入数据库连接配置文件 -->
<properties resource="db.properties" /> <settings>
<!-- MyBatis默认没有开启延迟加载器,
只需在配置文件中打开延迟加载的开关,然后无需在映射文件中再做配置 -->
<setting name="lazyLoadingEnabled" value="true" /> <!-- 将积极加载改为消息加载,即按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings> <!--使用扫描包的形式定义别名
<typeAliases>元素的子元素<package>中的name属性用于指定要被定义别名的包,
MyBatis会将所有com.itheima.po包中的POJO类以首字母小写的非限定类名(如:String)来作为它的别名,
比如com.itheima.po.User的别名为user,com.itheima.po.Customer的别名是customer等。
-->
<typeAliases>
<package name="com.itheima.po" />
</typeAliases> <!--配置环境 ,默认的环境id为mysql -->
<environments default="mysql"> <!-- 配置id为mysql的数据库环境 -->
<environment id="mysql"> <!-- 使用JDBC的事务管理 -->
<transactionManager type="JDBC" /> <!--数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource> </environment> </environments> <!--最后要配置Mapper的位置 -->
<mappers>
<mapper resource="com/itheima/mapper/IdCardMapper.xml" />
<mapper resource="com/itheima/mapper/PersonMapper.xml" />
<mapper resource="com/itheima/mapper/UserMapper.xml" />
<mapper resource="com/itheima/mapper/OrdersMapper.xml" />
<mapper resource="com/itheima/mapper/ProductMapper.xml" />
</mappers>
</configuration>
⑥一对一映射查询测试:src/com/itheima/test/MybatisAssociatedTest.java
package com.itheima.test;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test; import com.itheima.po.Orders;
import com.itheima.po.Person;
import com.itheima.po.User;
import com.itheima.utils.MybatisUtils;
/**
* Mybatis关联查询映射测试类
*/
public class MybatisAssociatedTest {
/**
* 嵌套查询
*/
@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();
} /**
* 嵌套结果
*/
@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();
}
}
⑦嵌套查询如图所示:
⑧嵌套结果如图所示:
3、一对多
1)、开发人员接触更多的关联关系是一对多(或多对一)。例如,一个用户可以有多个订单,同时多个订单归一个用户所有。<resultMap>元素中,包含了一个<collection>子元素,MyBatis就是通过该元素来处理一对多关联关系的。
2)、<collection>子元素的属性大部分与<association>元素相同,但其还包含一个特殊属性--ofType 。
3)、ofType属性与javaType属性对应,它用于指定实体对象中集合类属性所包含的元素类型。<collection >元素的使用也非常简单,同样可以参考如下两种示例进行配置,具体代码如下:
4)、创建数据库表tb_user和tb_orders:
CREATE TABLE tb_user(
id INT(32) PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
address VARCHAR(256)
);
INSERT INTO tb_user VALUES('', '张三', '深圳');
INSERT INTO tb_user VALUES('', '李四', '东莞'); CREATE TABLE tb_orders(
id INT(32) PRIMARY KEY AUTO_INCREMENT,
number VARCHAR(32) NOT NULL, #订单号
user_id INT(32) NOT NULL, #外键,该订单上客户id
FOREIGN KEY(user_id) REFERENCES tb_user(id) # 定义外键引用
);
INSERT INTO tb_orders VALUES('', '', '');
INSERT INTO tb_orders VALUES('', '', '');
执行SQL查询语句后如图所示:
①创建订单持久化类:src/com/itheima/po/Orders.java
package com.itheima.po; import java.util.List; /**
* 订单持久化类
*/
public class Orders {
private Integer id; //订单id
private String number; //订单编号 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 + "]";
} }
②创建用户持久化类:src/com/itheima/po/User.java
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 + "]";
}
}
③创建用户实体映射文件:src/com/itheima/mapper/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"> <!-- <mapper>是根元素,包括一个属性namespace表示命名空间,通常会设置成“包名+SQL映射文件名”的形式 -->
<mapper namespace="com.itheima.mapper.UserMapper"> <!-- 一对多:查看某一用户及其关联的订单信息
注意:当关联查询出的列名相同,则需要使用别名区分!!!
以下使用嵌套结果的方式定义了一个根据用户id查询用户及其关联的订单信息的select语句。
因为返回的用户对象中,包含Orders集合对象的属性,所以需要手动编写结果映射信息。
-->
<select id="findUserWithOrders" parameterType="Integer"
resultMap="UserWithOrdersResultID">
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="UserWithOrdersResultID"> <id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/> <!-- 一对多关联映射:<collection>元素:对关联查询到多条记录映射到集合对象中
<collection>元素中property属性表示:将关联查询到多条记录映射到User类中的订单对象实例属性ordersList
ofType表示属性集合中元素的类型,这里的List<Orders>属性即Orders类
<id>元素表示订单明细的唯一标识;
<result>元素表示其他属性和数据库做一一映射
-->
<collection property="ordersList" ofType="Orders">
<!-- 下面是Orders实体类与数据库表做一个映射关系
注意:column属性应该和编写复杂SQL语句字段对应,否则会对应不上正确数据
-->
<id property="id" column="orders_id"/>
<result property="number" column="number"/>
</collection>
</resultMap>
</mapper>
④单元测试:
/**
* 一对多
*/
@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();
}
⑤执行结果:
4、多对多
1)、在实际项目开发中,多对多的关联关系也是非常常见的。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单。
2)、在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表中的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。
3)、在MyBatis中,多对多的关联关系查询,同样可以使用前面介绍的<collection >元素进行处理(其用法和一对多关联关系查询语句用法基本相同)。
4)、创建数据库表tb_product和tb_ordersitem:
CREATE TABLE tb_product(
id INT(32) PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32),
price DOUBLE
);
INSERT INTO tb_product VALUES('', 'JAVA入门基础', '');
INSERT INTO tb_product VALUES('', 'JAVAWEB程序开发入门', '');
INSERT INTO tb_product VALUES('', 'SSM框架整合实战', ''); 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('', '', '');
INSERT INTO tb_ordersitem VALUES('', '', '');
INSERT INTO tb_ordersitem VALUES('', '', ''); #注意引用值的存在
①创建商品持久化类:src/com/itheima/po/Product.java
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 + "]";
}
}
②创建订单持久化类:src/com/itheima/po/Orders.java
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;
} 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 + "]";
}
}
③创建订单实体映射文件:src/com/itheima/mapper/OrdersMapper.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.OrdersMapper"> <!-- 多对多嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型,查询订单id中的商品信息 -->
<select id="findOrdersWithPorduct" parameterType="Integer"
resultMap="OrdersWithProductResultID">
select * from tb_orders WHERE id=#{id}
</select> <resultMap type="Orders" id="OrdersWithProductResultID"> <id property="id" column="id" />
<result property="number" column="number" /> <!-- 使用<collection>来映射多对多的关系关联
property属性表示订单持久化类Orders中的商品属性productList,
ofType表示集合productList中数据类型为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="OrdersWithPorductResult2ID">
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="OrdersWithPorductResult2ID"> <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>
④创建商品映射文件:src/com/itheima/mapper/ProductMapper.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.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}
<!-- tb_ordersitem为中间表,通过中间表的orders_id和订单中的id进行匹配找商品id
找订单编号id中所有商品的id值
-->
)
</select>
</mapper>
⑤多对多关联查询测试:
/**
* 多对多
*/
@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();
}
⑥执行结果:
个人总结:
Mybatis的关联映射很好地模拟了数据库多表查询,感觉很方便,初学还不太熟悉,应该多操作,多理解!
最新文章
- 【Python】pymongo使用
- [Linux]yum开启rpm包缓存
- Build 2016概览
- Asp.net面试题
- Remember-Me功能
- Python3批量爬取网页图片
- javascript(五)验证
- windows下Jdk和Tomcat的安装配置
- SecureFX 乱码问题
- Activiti 用户任务关联自定义表单
- CDH5.11..0安装
- 网络爬虫技术Jsoup——爬到一切你想要的(转)
- linux 内存-文档学习
- 遍历结构体内部元素和值(Name and Value)
- C语言入坑指南-被遗忘的初始化
- Linux安装JDK(tar)
- JetBrain(Pycharm,Clion...)的使用优化
- sqlserver数据库命名规则
- 使用R的数据库查询
- kubernetes 学习 service相关