04ssm_Mybaits关联映射和缓存机制
一、关联映射概述
在实际开发中,对数据库的操作常常会涉及到多张表,针对多表之间的操作,MyBatis提供了关联映射,通过关联映射可以很好地处理表与表、对象与对象之间的关联关系。
1.数据表的三种关联映射关系
三种映射关系分别为:一对一关系、一对多关系、多对多关系。
2.Java描述数据表的关联映射关系
具体描述方法如下图所示。
3.本单元实验环境
- 第一步,在ssm_mybatis项目下建mybatis-chap04-relation&cache模块,目录结构如下:
-
第二步,导入mybatis、mysql、junit包
-
第三步,创建数据库mybatis
- 建表tb_idcard。建表语句如下:
USE mybatis;
# 创建一个名称为tb_idcard的表
CREATE TABLE tb_idcard(
id INT PRIMARY KEY AUTO_INCREMENT,
CODE VARCHAR(18)
);
# 插入2条数据
INSERT INTO tb_idcard(CODE) VALUES('152221198711020624');
INSERT INTO tb_idcard(CODE) VALUES('152201199008150317');
- 建表tb_person。建表语句如下:
USE mybatis;
# 创建一个名称为tb_person的表
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)
);
# 插入2条数据
INSERT INTO tb_person(name,age,sex,card_id) VALUES('Rose',22,'女',1);
INSERT INTO tb_person(name,age,sex,card_id) VALUES('jack',23,'男',2);
- 建表tb_user。建表语句如下:
USE mybatis;
# 创建一个名称为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。建表语句如下:
USE mybatis;
# 创建一个名称为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_product。建表语句如下:
USE mybatis;
# 创建一个名称为tb_product的表
CREATE TABLE tb_product (
id INT(32) PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32),
price DOUBLE
);
# 插入3条数据
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');
- 建表tb_ordersitem。建表语句如下:
USE mybatis;
# 创建一个名称为tb_ordersitem 的中间表
CREATE TABLE tb_ordersitem (
id INT(32) PRIMARY KEY AUTO_INCREMENT,
orders_id INT(32),
productb_id INT(32),
FOREIGN KEY(orders_id) REFERENCES tb_orders(id),
FOREIGN KEY(productb_id) REFERENCES tb_product(id)
);
# 插入3条数据
INSERT INTO tb_ordersitem VALUES ('1', '1', '1');
INSERT INTO tb_ordersitem VALUES ('2', '1', '3');
INSERT INTO tb_ordersitem VALUES ('3', '3', '3');
- 建表tb_book。建表语句如下:
USE mybatis;
# 创建一个名称为tb_book的表
CREATE TABLE tb_book(
id INT PRIMARY KEY AUTO_INCREMENT,
bookName VARCHAR(255),
price double,
author VARCHAR(40)
);
# 插入3条数据
INSERT INTO tb_book(bookName,price,author) VALUES('Java基础入门',45.0,'传智播客高教产品研发部');
INSERT INTO tb_book(bookName,price,author) VALUES('Java基础案例教程',48.0,'黑马程序员');
INSERT INTO tb_book(bookName,price,author) VALUES('JavaWeb程序设计任务教程',50.0,'黑马程序员');
- 第四步,创建POJO类**(后续根据不同需求在pojo包中创建)**
- 第五步,创建各POJO类的mapper接口**(后续根据不同需求在mapper包中创建)**
- 第六步,为上述接口创建映射文件**(后续根据不同需求在resources\mapper文件夹中创建)**
- 第七步,创建Mybatis核心配置文件mybatis-config.xml(放resources文件夹)
- 创建db.properties,配置数据库连接各属性(放resources文件夹)
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&character=utf-8
mysql.username=root
mysql.password=123456
- 创建mybatis-config.xml(**放resources文件夹**),其中的mapper及扫描别名配置根据需要后续再配置
<?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元素-->
<properties resource="db.properties"/>
<!--采用包扫描方式定义别名。其内部可以配置多个package元素-->
<typeAliases>
<package name="com.cshbxy.pojo"/>
</typeAliases>
<!--配置数据源。可以多套environment,由default属性仁决定使用哪套!-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件。其内部可以配置多个mapper元素-->
<mappers>
<!--后续根据实际需要通过mapper元素引用-->
</mappers>
</configuration>
- 第八步,创建工具类MyTool,用以获取会话对象
- 创建com.cshbxy.util包
- 创建工具类MyTool
package com.cshbxy.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyTool {
private static SqlSessionFactory factory;
static {
try {
//创建会话工厂对象factory
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return factory.openSession(true);
}
}
- 第九步,编写测试类(放test\java文件夹下,不同的mapper建一个测试类),此处给模板,后续自己按模板添加单元测试
import com.cshbxy.mapper.PersonMapper;
import com.cshbxy.pojo.Person;
import com.cshbxy.util.MyTool;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class TestPersonMapper {
@Test
public void test01() throws IOException {
//第一大步,创建会话对象sqlSession
SqlSession sqlSession = MyTool.getSqlSession();
//第二大步,通过会话对象的getMapper()方法实现接口CustomerMapper,并获取该对象
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
//第三大步,完成测试(此处自己针对实现的方法进行测试)
//第四大步,关闭会话对象。因为之前创建事务时带true参数,所以此处不必考虑事务提交问题
sqlSession.close();
}
}
二、一对一查询(重点掌握)
1.需求描述
按id查询tb_person数据表中每个人的身份证信息。相关表tb_person、tb_idcard数据如下:
id | name | age | sex | card_id |
---|---|---|---|---|
1 | Rose | 22 | 女 | 1 |
2 | jack | 23 | 男 | 2 |
id | code |
---|---|
1 | 152221198711020624 |
2 | 152201199008150317 |
2.任务分析
- tb_idcard的主键id为身份证的id号,code为其身份证编码;
- tb_person的主键id,外键card_id为其身份证的id号;
- tb_person的外键card_id与tb_idcard的主键id关联;
- 每个人只有一个身份证编码,一个身份证编码也只能属于一个人。即:构成了一对一的关联关系。
3.知识准备:实现一对一映射关系的两种方式
- 要点: 怎么设计实体类,表现一对一关系
- 最核心要解决的问题: 是类类型属性与查询结果字段的映射,这种映射关系通过元素的子元素完成。同时,还应将查询元素的resultMap属性赋值为配置的映射关系的id值。
- 有两种解决方式: 嵌套查询方式、嵌套结果方式
(1)元素
具有如下属性。
****属性 | ****说明 |
---|---|
****property | 用于指定****映射到的实体类对象的属性,与表字段一一对应 |
****column | 用于****指定表中对应的字段 |
****javaType | 用于指定****映射到实体对象的属性的类型 |
jdbcType | 用于指定数据表中对应字段的类型 |
fetchType | 用于指定在关联查询时是否启用延迟加载。fetchType属性有lazy和eager两个属性值,默认值为lazy |
****select | 用于****指定引入嵌套查询的子SQL语句 |
autoMapping | 用于指定是否自动映射 |
typeHandler | 用于指定一个类型处理器 |
(2)嵌套查询方式
通过执行另外一条SQL映射语句来返回预期的类类型(实体类类型)-- 对应通过子查询完成关联关系的SQL语句。
<!--resulMap各属性及子元素说明
id属性 指结果映射id号,与上面select元素的resultMap属性值对应
type属性 指java中定义的实体类名或该类的别名
id元素及result元素 配置type指定实体类的各属性与查询结果各字段的映射关系,其中id元素用以主键的映射。
-->
<resultMap id="person" type="person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!--association子元素属性说明
property属性 需要映射的复杂类型属性名
column属性 可理解为:指定select属性子查询接收参数的来源
javaType属性 property指定属性的类型
select属性 要执行的SQL映射id
-->
<association property="card" column="card_id" javaType="IdCard"
select="com.cshbxy.mapper.IdCardMapper.selectById"/>
</resultMap>
(3)嵌套结果方式(重点掌握)
使用嵌套结果映射来处理重复的联合结果的子集 – 对应多表联合查询SQL语句。
<resultMap id="person" type="person">
<id property="id" column="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>
4.实验步骤
第一步,创建POJO类(注意:在com.cshbxy.pojo包内创建)
- 创建IdCard类,用以描述 tb_idcard数据表
package com.cshbxy.pojo;
public class IdCard {
private int id;
private String code;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public IdCard(int id, String code) {
this.id = id;
this.code = code;
}
@Override
public String toString() {
return "IdCard{" +
"id=" + id +
", code='" + code + '\'' +
'}';
}
}
- 创建Person类,用以描述数据表tb_person与tb_idcard数据表之间的一对一关系。
请注意:设计一对一关联类时,将外键字段card_id换成了关联表的POJO类对象
package com.cshbxy.pojo;
//设计一对一关联类时,将外键字段换成了关联表的POJO类对象
public class Person {
private int id;
private String name;
private int age;
private String sex;
private IdCard card; //通过card字段将tb_person表关联到tb_idcard表
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int 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 +
'}';
}
}
注:实际项目中,该类可能会放在vo包或bo包中。
第二步,创建接口PersonMapper(注意:在com.cshbxy.mapper包中创建)
package com.cshbxy.mapper;
import com.cshbxy.pojo.Person;
public interface PersonMapper {
/*按id查询tb_person数据表中每个人的身份证信息*/
Person findPersonById(int id);
}
第三步,编写映射文件(注意:在resources\mapper文件夹中创建)
- 关键:如何完成实体类Person的card属性与查询结果间的映射!
- 有两种方式
- 嵌套查询方式
- 首先,在IdCardMapper.xml映射文件中创建被关联表tb_idcard的按主键id查询的SQL映射语句
- 嵌套查询方式
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cshbxy.mapper.IdCardMapper">
<!--嵌套查询方式-->
<!--第一步,创建关联表按主键查询的SQL映射语句。-->
<!--按id查询身份证信息-->
<select id="selectById" resultType="IdCard">
select * from mybatis.tb_idcard
where id=#{id}
</select>
</mapper>
- 之后,在**PersonMapper.xml映射文件**中完成按id进行的一对一关联查询,同时要通过<resultMap>元素及其子元素<association>对结果进行映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cshbxy.mapper.PersonMapper">
<!--嵌套查询方式-->
<!--第二步,按id进行一对一关联查询,同时要通过resultMa元素及其子元素association对结果进行映射-->
<!--按id查询tb_person数据表中每个人的身份证信息。对应前述接口中的findPersonById()方法。注:要使用resultMap属性,而不是resultType属性-->
<select id="findPersonById" resultMap="person">
select * from mybatis.tb_person
where id=#{id}
</select>
<!--resulMap各属性及子元素说明
id属性 指结果映射id号,与上面select元素的resultMap属性值对应
type属性 指java中定义的实体类名或该类的别名
id元素及result元素 配置type指定实体类的各属性与查询结果各字段的映射关系,其中id元素用以主键的映射。
-->
<resultMap id="person" type="person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!--association子元素属性说明
property属性 需要映射的复杂类型属性名
column属性 可理解为:指定select属性子查询接收参数的来源
javaType属性 property指定属性的类型
select属性 要执行的SQL映射id
-->
<association property="card" column="card_id" javaType="IdCard"
select="com.cshbxy.mapper.IdCardMapper.selectById"/>
</resultMap>
</mapper>
- 嵌套结果方式**(重点掌握)**
- 在**PersonMapper.xml映射文件**中完成按id进行的一对一关联查询,同时要通过<resultMap>元素及其子元素<association>对结果进行映射。该查询语句要用**多表联合查询的书写方式**
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cshbxy.mapper.PersonMapper">
<!--嵌套结果方式:嵌套结果映射处理重复的联合结果的子集-->
<select id="findPersonById" resultMap="person">
select a.*,b.code from mybatis.tb_person a
join mybatis.tb_idcard b
on a.card_id=b.id
where a.id=#{id}
</select>
<resultMap id="person" type="person">
<id property="id" column="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>
</mapper>
第四步,核心配置文件中引入第三步中创建的映射文件。
- 如果是嵌套查询方式,要在元素中引入IdCardMapper.xml、PersonMapper.xml映射文件
<mapper resource="mapper/IdCardMapper.xml"/>
<mapper resource="mapper/PersonMapper.xml"/>
- 如果是嵌套结果方式,只要在元素中引入PersonMapper.xml映射文件
<mapper resource="mapper/PersonMapper.xml"/>
第五步,完成测试。(按前述提示自行完成!)
测试:查询id值为2的身份信息,结果如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7SViRIP-1653758110282)(https://secure2.wostatic.cn/static/oNFZZDx5WFkFEgNJKML7di/9df5b9_xo1.png)]
三、一对多查询(重点掌握)
1.需求描述
**按id查询tb_user数据表中每个人的订单信息。**相关表tb_user、tb_orders数据如下:
id | username | address |
---|---|---|
1 | 小明 | 北京 |
2 | 李华 | 上海 |
3 | 李刚 | 上海 |
id | number | user_id |
---|---|---|
1 | 1000011 | 1 |
2 | 1000012 | 1 |
3 | 1000013 | 2 |
2.任务分析
- tb_user的主键id为用户的id号;
- tb_orders的主键为id为订单的id号,外键user_id为该订单所属用户的id;
- tb_user主键id与tb_orders的外键user_id关联
- 每位客户均可以有多条订单信息,但一个订单只能属于一个客户。即:构成了一对多的关联关系。
3.知识准备:实现一对多映射关系的两种方式
- 要点: 怎么设计实体类,表现一对多关系
- 最核心要解决的问题: 是集合类型属性与查询结果字段的映射,这种映射关系通过元素的子元素完成。同时,还应将查询元素的resultMap属性赋值为配置的映射关系的id值。
- 有两种解决方式: 嵌套查询方式、嵌套结果方式
(1)元素
- 大部分属性与前述元素相同
- ofType属性:指定实体类对象中集合类属性所包含的元素的类型
(2)嵌套查询方式
通过执行另外一条SQL映射语句来返回预期的类类型(实体类类型)-- 对应通过子查询完成关联关系的SQL语句。
<resultMap id="user" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<!--ofType属性:指实体类User的集合属性对象中元素的类型。column属性值:子查询参数来源于User表中的哪个字段-->
<collection property="ordersList" column="id" ofType="Orders"
select="com.cshbxy.mapper.OrdersMapper.selectByUserId"/>
</resultMap>
(3)嵌套结果方式(重点掌握)
使用嵌套结果映射来处理重复的联合结果的子集 – 对应多表联合查询完成关联关系的SQL语句。
<resultMap id="user" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<!--ofType属性:指实体类User的集合属性对象中元素的类型-->
<collection property="ordersList" ofType="Orders">
<id property="id" column="orderId"/>
<result property="number" column="number"/>
<result property="user_id" column="user_id"/>
</collection>
</resultMap>
4.实验步骤
第一步,创建POJO类(注意:在com.cshbxy.pojo包内创建)
- 创建Orders类,用以描述 tb_orders数据表
package com.cshbxy.pojo;
public class Orders {
private int id;
private String number;
private int user_id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
@Override
public String toString() {
return "\n\tOrders{" +
"id=" + id +
", number='" + number + '\'' +
", user_id=" + user_id +
'}';
}
}
- 创建User类,用以描述数据表tb_user与tb_orders数据表之间的一对多关系。
请注意:设计一对多关联类时,在主键表对应的实例类中增加一个元素类型为Orders的集合对象以描述一对多关系
package com.cshbxy.pojo;
import java.util.List;
public class User {
private int id;
private String username;
private String address;
private List<Orders> ordersList; //每位客户均可以有多个订单
public int getId() {
return id;
}
public void setId(int 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 + '\'' +
", \n\tordersList=" + ordersList +
"\n}";
}
}
注:实际项目中,该类可能会放在vo包或bo包中。
第二步,创建接口UserMapper(注意:在com.cshbxy.mapper包中创建)
package com.cshbxy.mapper;
import com.cshbxy.pojo.User;
import java.util.List;
public interface UserMapper {
/*按id查询tb_user数据表中每个人的订单信息*/
User findUserOrdersById(int id);
}
第三步,编写映射文件(注意:在resources\mapper文件夹中创建)
- 关键:如何完成实体类User的ordersList属性与查询结果间的映射!
- 有两种方式
- 嵌套查询方式
- 首先,在OrdersMapper.xml映射文件中创建被关联表tb_Orders的按user_id查询的SQL映射语句
- 嵌套查询方式
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cshbxy.mapper.OrdersMapper">
<!--按user_id查询订单信息-->
<select id="selectByUserId" resultType="Orders">
select * from mybatis.tb_orders
where user_id=#{user_id}
</select>
</mapper>
- 之后,在**UserMapper.xml映射文件**中完成按id进行的一对多关联查询,同时要通过<resultMap>元素及其子元素<collection>对结果进行映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cshbxy.mapper.UserMapper">
<!--按id查询tb_user数据表中每个人的订单信息-->
<!--嵌套查询方式-->
<select id="findUserOrdersById" resultMap="user">
select * from mybatis.tb_user
where id=#{id}
</select>
<resultMap id="user" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<!--ofType属性:指实体类User的集合属性对象中元素的类型。column属性值:子查询参数来源于User表中的哪个字段-->
<collection property="ordersList" column="id" ofType="Orders"
select="com.cshbxy.mapper.OrdersMapper.selectByUserId"/>
</resultMap>
</mapper>
- 嵌套结果方式**(重点掌握)**
- 在**UserMapper.xml映射文件**中完成按id进行的一对多关联查询,同时要通过<resultMap>元素及其子元素<collection>对结果进行映射。该查询语句要用**多表联合查询的书写方式**
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cshbxy.mapper.UserMapper">
<!--按id查询tb_user数据表中每个人的订单信息-->
<!--嵌套结果方式。如果查询结果字段有同名时,要取别名以视区别!-->
<select id="findUserOrdersById" resultMap="user">
select a.*,b.id orderId,b.number,b.user_id from mybatis.tb_user a
join mybatis.tb_orders b
on a.id=b.user_id
where a.id=#{id}
</select>
<resultMap id="user" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<!--ofType属性:指实体类User的集合属性对象中元素的类型-->
<collection property="ordersList" ofType="Orders">
<id property="id" column="orderId"/>
<result property="number" column="number"/>
<result property="user_id" column="user_id"/>
</collection>
</resultMap>
</mapper>
第四步,核心配置文件中引入第三步中创建的映射文件。
- 如果是嵌套查询方式,要在元素中引入OrdersMapper.xml、UserMapper.xml映射文件
<mapper resource="mapper/OrdersMapper.xml"/>
<mapper resource="mapper/UserMapper.xml"/>
- 如果是嵌套结果方式,只要在元素中引入PersonMapper.xml映射文件
<mapper resource="mapper/UserMapper.xml"/>
第五步,完成测试。(按前述提示自行完成!)
测试:查询id值为1的客户订单信息,结果如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hf0v6YZy-1653758110283)(https://secure2.wostatic.cn/static/pPX3h2iApsVkrzwdow8WbR/i2ey2jocmu.png)]
四、多对多查询
1.需求描述
按订单号查询该订单有哪些商品。 关联表tb_product、tb_ordersitem数据如下:
id | name | price |
---|---|---|
1 | Java基础入门 | 44.5 |
2 | Java Web程序开发入门 | 38.5 |
3 | SSM框架整合实战 | 50 |
id | orders_id | product_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 3 |
3 | 3 | 3 |
2.任务分析
- tb_ordersitem的订单号为外键orders_id,该订单包含的商品id号为外键product_id;
- tb_product的主键为id即为商品的id号;
- tb_ordersitem的product_id与tb_product的id字段关联;
- 一个订单有多种商品,一种商品可以属性多个订单,两表间构成了多对多关联关系。
3.知识准备:实现多对多映射关系的两种方式
- 要点: 怎么设计实体类,表现多对多关系
- 最核心要解决的问题: 是类类型属性与查询结果字段的映射,这种映射关系通过元素的子元素完成。同时,还应将查询元素的resultMap属性赋值为配置的映射关系的id值。
- 有两种解决方式: 嵌套查询方式、嵌套结果方式。实现方法与一对多类似!
4.实验步骤
第一步,创建POJO类(注意:在com.cshbxy.pojo包内创建)
- 创建Product类,用以描述 tb_product数据表
package com.cshbxy.pojo;
public class Product {
private int id;
private String name;
private double price;
public int getId() {
return id;
}
public void setId(int 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;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}
}
- 创建OrdersItem类,用以描述数据表一个orders_id包含多咱商品。之后查询结果为集合即可表现多对多关系!
package com.cshbxy.pojo;
import com.cshbxy.pojo.Product;
import java.util.List;
public class OrdersItem {
private int id;
private int orders_id;
private List<Product> productList; //一个orders_id包含多种商品
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getOrders_id() {
return orders_id;
}
public void setOrders_id(int orders_id) {
this.orders_id = orders_id;
}
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
@Override
public String toString() {
return "OrdersItem{" +
"id=" + id +
", orders_id=" + orders_id +
", productList=" + productList +
'}';
}
}
注:实际项目中,该类可能会放在vo包或bo包中。
第二步,创建接口OrdersItemMapper(注意:在com.cshbxy.mapper包中创建)
package com.cshbxy.mapper;
import com.cshbxy.pojo.OrdersItem;
import java.util.List;
public interface c {
/*按订单号查询该订单有哪些商品*/
List<OrdersItem> findProductByOrdersId();
}
第三步,编写映射文件(注意:在resources\mapper文件夹中创建)
- 关键:如何完成实体类OrdersItem的productList属性与查询结果间的映射!
- 有两种方式
- 嵌套查询方式**(自行根据一对多关系实现方法完成!)**
- 嵌套结果方式
- 在OrdersItemMapper.xml映射文件中完成按id进行的一对一关联查询,同时要通过元素及其子元素对结果进行映射。该查询语句要用多表联合查询的书写方式
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cshbxy.mapper.OrdersItemMapper">
<!--按订单号查询该订单有哪些商品-->
<!--嵌套结果方式-->
<select id="findProductByOrdersId" resultMap="ordersItem">
select a.*,b.id oid,b.orders_id,b.product_id from mybatis.tb_product a
join mybatis.tb_ordersitem b
on a.id=b.product_id
where orders_id=#{id}
</select>
<resultMap id="ordersItem" type="ordersItem">
<id property="id" column="oid"/>
<result property="orders_id" column="orders_id"/>
<collection property="productList" ofType="Product">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
</mapper>
第四步,核心配置文件中引入第三步创建的映射文件。
- 如果是嵌套查询方式,自行根据具体情况完成映射文件的引入!
- 如果是嵌套结果方式,在元素中引入OrdersItemMapper.xml映射文件
<mapper resource="mapper/OrdersItemMapper.xml"/>
第五步,完成测试。(按前述提示自行完成!)
测试:查询订单号orders_id值为1的订单包含的商品信息,结果如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jynuhKE3-1653758110285)(https://secure2.wostatic.cn/static/igxZzAJDBPDAYAbjm8cdWv/vpjaoeizrs.png)]
思考:如查要按商品id查询有哪些订单包含该商品应如何用Mybaits实现。
五、Mybatis缓存机制
在实际开发中经常需要合理地利用MyBatis缓存来加快数据库查询,进而有效地提升数据库性能。
1.补充:Mybatis日志输出
(1)使用Mybatis自带标准日志
在在MyBatis核心配置文件中加入如下配置。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
(2)使用log4j日志输出
第一步,在MyBatis核心配置文件中加入LOG4J的日志配置。
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
第二步,resources目录下创建log4j.properties文件**(课后查阅log4j属性配置方法!)**
#全局日志配置
log4j.rootLogger=DEBUG, Console
#控制台输出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#日志输出级别,只展示了一个
log4j.logger.java.sql.PreparedStatement=DEBUG
2.一级缓存
(1)特点
- MyBatis的一级缓存是SqlSession级别的缓存
- 如果同一个SqlSession对象多次执行完全相同的SQL语句时,在第一次执行完成后,MyBatis会将查询结果写入到一级缓存中,当第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不用再去数据库查询,从而提高了数据库的查询效率
- 当程序对数据库执行了插入、更新、删除操作,MyBatis会清空一级缓存中的内容以防止程序误读
(2)开启/关闭方法
- Mybatis默认开启一级缓存
- 关闭一级缓存有如下方法
- 在映射文件的select标签中设置flushCache=“true”
- 在核心配置文件的元素中配置localCacheScope属性值为STATEMENT
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
(3)学会如何看是否启用了一级缓存 (自己写测试案例通过日志来看)
3.二级缓存
(1)特点
- MyBatis的二级缓存是Mapper级别的缓存
- 多个SqlSession对象使用同一个Mapper的相同查询语句去操作数据库,在第一个SqlSession对象执行完后,MyBatis会将查询结果写入二级缓存,当第二个SqlSession对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。
- 当程序对数据库执行了插入、更新、删除操作,MyBatis会清空二级缓存中的内容以防止程序误读
- 要求相关的POJO类实现了序列化(implements Serializable)
(2)开启/关闭方法
- Mybatis默认关闭二级缓存
- 开启二级缓存步骤
- 第一步,开启二级缓存的全局配置,在MyBatis的核心配置进行如下配置。
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
- 第二步,开启当前Mapper的namespace下的二级缓存。可以通过映射文件中的<cache>元素来完成。
<!-- 开启当前Mapper的namespace下的二级缓存-->
<cache></cache>
(3)学会如何看是否启用了二级缓存 (自己写测试案例通过日志来看)
案例:商品的类别
现有一个商品表product(商品编号、商品名称、商品单价、商品类别)和一个商品类别表category(商品类别编号、商品类别名称),其中,商品类别表category和商品表product是一对多的关系。各表具体情况如下:
商品编号 id | 商品名称 ****goodsname | 商品单价 price | 商品类别 typeid |
---|---|---|---|
1 | 电视机 | 5000 | 1 |
2 | 冰箱 | 4000 | 2 |
3 | 空调 | 3000 | 2 |
4 | 洗衣机 | 2000 | 2 |
商品类别编号 id | 商品类别名称 typename |
---|---|
1 | 黑色家电 |
2 | 白色家电 |
数据表生成代码如下:
USE mybatis;
# 创建一个名称为category的表
CREATE TABLE category (
id int(32) PRIMARY KEY AUTO_INCREMENT,
typename varchar(40)
);
# 插入2条数据
INSERT INTO category VALUES (1, '黑色家电');
INSERT INTO category VALUES (2, '白色家电');
# 创建一个名称为product的表
CREATE TABLE product (
id int(32) PRIMARY KEY AUTO_INCREMENT,
goodsname varchar(40),
price DOUBLE,
category_id int(32) NOT NULL,
FOREIGN KEY(category_id) REFERENCES category(id)
);
# 插入4条数据
INSERT INTO product VALUES (1, '电视机', 5000,1);
INSERT INTO product VALUES (2, '冰箱', 4000,2);
INSERT INTO product VALUES (3, '空调', 5000,2);
INSERT INTO product VALUES (4, '洗衣机', 2000,2);
要求: 通过MyBatis查询商品类别为白色家电的商品的所有信息。