【Java_EE】Day04 MyBatis的关联映射和缓存机制
MyBatis的关联映射和缓存机制
一对一查询
主键数据表中的一条记录最多可以与另一个数据表的一条数据相关;例如一个人只能有一个身份证,同时一个身份证也只对应一个人。
在MyBatis中,通过<association>
元素来处理一对一关联关系。<association>
元素提供了一系列属性用于维护数据表之间的关系。属性如下:
属性 | 说明 |
---|---|
property | 用于指定映射到实体类对象的属性,要求与表字段一一对应 |
column | 用于指定表中对应的字段 |
javaType | 用于指定映射到实体对象的属性的类型 |
jdbcType | 用于指定数据表中对应字段的类型 |
fetchType | 用于指定在关联查询时是否启用延时加载,包含值:lazy(懒汉)和eager(饿汉),默认值为lazy(懒汉)(默认关联映射延迟加载) |
select | 用于指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询 |
autoMapping | 用于指定是否自动映射 |
typeHandler | 用于指定一个类型处理器 |
<association>
元素是<resultMap>
的子元素,他有两种配置方式,分别是:嵌套查询和嵌套结果。
嵌套查询方式
- 先准备一下数据,在
mybatis
数据库中分别创建名称为tb_idcard
的身份证数据表和名称为tb_person
的个人数据表,同时预先插入两条数据,具体的SQL语句如下:
# 创建数据表 tb_idcard和tb_person同时插入数据
USE mybatis;
# 创建一个名称为tb_idcard的表
CREATE TABLE tb_idcard(
id INT PRIMARY KEY AUTO_INCREMENT,
CODE VARCHAR(18)
);
# 插入两条数据
INSERT INTO tb_idcard (CODE) VALUES ('152221198711020624');
INSERT INTO tb_idcard (CODE) VALUES ('152201199008150317');
# 创建一个名称为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)
);
# 插入两条数据
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);
- 创建
POJO
对象,分别在com.itheima.pojo
包下创建IdCard.java
类及Person.java
类:
IdCard.java
package com.itheima.pojo;
/**
* @author Zhang
*/
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 + '\'' +
'}';
}
}
Person.java
package com.itheima.pojo;
/**
* @author Zhang
*/
public class Person {
private Integer id;
private String name;
private Integer age;
private String sex;
private IdCard card;
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", card=" + 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;
}
}
- 在
com.itheima.mapper
包中创建“身份证映射文件”IdCardMapper.xml
,并在映射文件中编写一对一关联映射查询的配置信息。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="com.itheima.pojo.IdCard">
SELECT id, CODE
FROM mybatis.tb_idcard
WHERE id = #{id}
</select>
</mapper>
- 在
com.itheima.mapper
包中创建“人员映射文件”PersonMapper.xml
,并在映射文件中编写一对一关联映射查询的配置信息。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映射语句来返回预期的特殊类型-->
<select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult">
SELECT *
FROM mybatis.tb_person
WHERE id = #{id}
</select>
<resultMap id="IdCardWithPersonResult" type="Person">
<id property="id" column="id" />
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!-- 一对一映射关系:association使用select属性引入另外一条SQL语句-->
<association property="card" column="card_id" javaType="IdCard" select="com.itheima.mapper.IdCardMapper.findCodeById"/>
</resultMap>
</mapper>
- 在核心配置文件
mybatis-config.xml
中,引入IdCardMapper.xml
和PersonMapper.xml映射文件,并为com.itheima.pojo
包下的所有实体类定义别名,代码如下:
<?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"/>
<!-- 使用扫描包的形式定义别名-->
<typeAliases>
<package name="com.itheima.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 使用db.properties文件中的配置 -->
<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>
<mappers>
<mapper resource="mapper/UserMapper.xml" />
<mapper resource="com/itheima/mapper/StudentMapper.xml"/>
<mapper resource="com/itheima/mapper/EmployeeMapper.xml"/>
<mapper resource="com/itheima/mapper/CustomerMapper.xml"/>
<mapper resource="com/itheima/mapper/IdCardMapper.xml"/>
<mapper resource="com/itheima/mapper/PersonMapper.xml"/>
</mappers>
</configuration>
- 在测试类
MyBatisTest.java
中,编写方法findPersonByIdTest()
,具体代码如下:
// 嵌套查询
@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();
}
但是MyBatis嵌套查询的方式要执行多条SQL语句,对于大型数据库集合和列表展示来说,这样可能会导致成百上千的SQL语句被执行,从而极大小号数据库性能并且会降低查询效率,这并不是开发人员期望的,接下来,我们通过Mybatis提供的嵌套结果方式进行关联查询:
- 在
com.itheima.mapper
包中PersonMapper.xml
增加findPersonById2
查询,并在映射文件中编写一对一关联映射查询的配置信息。PersonMapper.xml
具体代码如下:
<!-- 嵌套结果-使用嵌套结果映射来处理重复的联合结果的子集-->
<select id="findPersonById2" parameterType="Integer" resultMap="IdCardWithPersonResult2">
SELECT p.*, idcard.CODE
FROM mybatis.tb_person p, mybatis.tb_idcard idcard
WHERE p.card_id = idcard.id
AND p.id = #{id}
</select>
<resultMap id="IdCardWithPersonResult2" 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>
- 在测试类
MyBatisTest.java
中,编写方法findPersonByIdTest2()
,具体代码如下:
// 嵌套结果
@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();
}
MyBatis延迟加载
MyBatis延迟加载是指不需要在每个映射文件中单独配置延迟加载。具体来说,MyBatis延迟加载主要与一对一(association
)和一对多(collection
)的关联关系相关。默认情况下,MyBatis会立即加载所有关联的对象,但开启延迟加载后,这些关联对象仅在真正使用时才被加载。其目的是为了使MyBatis在一定程度上降低运行消耗并提高查询效率。
配置方式如下:
- 在核心配置文件
mybatis-config.xml
中的<configuration>
根元素中,<properties>
属性配置元素之后,增加或修改如下代码:
<settings>
<!-- 打开延迟加载的开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消息加载,即按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
一对多查询
开发人员接触较多的关联关系使一对多(或多对一)关系。例如:一个用户可以有多笔订单,但是一个订单不可以由多个用户所有。
在MyBatis中,通过<collection>
元素来处理一对多关联关系,<collection>
元素的属性基本与<association>
一样,只有一个特殊属性ofType
。该属性与javaType
属性相对应,用于指定实体类对象中集合类属性所包含的元素类型。<collection>
元素也有嵌套查询和嵌套结果两种配置方式。
下面介绍如何在MyBatis中处理一对多关联关系,具体步骤如下:
- 先在
mybatis
数据库中,创建两个数据表,分别为tb_user
(用户数据表)和tb_orders
(订单表),同时在表中预先插入几条测试数据,具体SQL语句如下:
# 在`mybatis`数据库中,创建两个数据表,分别为`tb_user`(用户数据表)和`tb_orders`(订单表),同时在表中预先插入几条测试数据
USE mybatis;
# 创建一个名称为tb_user的表
CREATE TABLE tb_user (
id INT(32) AUTO_INCREMENT PRIMARY KEY ,
username varchar(32) ,
address varchar(256)
);
# 插入三条数据
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) AUTO_INCREMENT PRIMARY KEY ,
user_id INT(32) NOT NULL ,
number VARCHAR(32) NOT NULL ,
FOREIGN KEY (user_id) REFERENCES tb_user (id)
);
# 插入三条数据
INSERT INTO tb_orders (id, user_id, number) VALUES ('1', '1', '1000011');
INSERT INTO tb_orders (id, user_id, number) VALUES ('2', '1', '1000012');
INSERT INTO tb_orders (id, user_id, number) VALUES ('3', '2', '1000013');
- 在
com.itheima.pojo
包中,创建持久化类Orders
,并定义属性,代码如下:
package com.itheima.pojo;
/**
* 订单持久化类
* @author Zhang
*/
public class Orders {
// 订单id
private Integer id;
// 订单编号
private String number;
@Override
public String toString() {
return "Orders{" +
"id=" + id +
", number='" + 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;
}
}
- 在
com.itheima.pojo
包下创建持久化类Users
,并定义数据表中对应的所有属性,代码如下:
package com.itheima.pojo;
import java.util.List;
/**
* 用户持久化类
* @author Zhang
*/
public class Users {
// 用户id
private Integer id;
// 用户名
private String username;
// 地址
private String address;
// 用户关联订单
private List<Orders> ordersList;
@Override
public String toString() {
return "Users{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", ordersList=" + 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;
}
}
- 在
com.itheima.mapper
包下,创建用户实体映射文件UsersMapper.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.UsersMapper">
<select id="findUserWithOrders" parameterType="Integer" resultMap="UserWithOrdersResult">
SELECT u.*, o.id AS orders_id, o.number
FROM mybatis.tb_user u , mybatis.tb_orders o
WHERE u.id = o.user_id
AND u.id = #{id}
</select>
<!-- 一对多:查看某用户及其关联的订单信息
注意:当关联查询出的列名相同时,要使用别名进行区分-->
<resultMap id="UserWithOrdersResult" type="Users">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
<!-- 一对多关联映射:collection
ofType:表示属性集合中元素的类型
List<Orders>属性即Orders类 -->
<collection property="ordersList" ofType="Orders">
<id column="orders_id" property="id"/>
<result column="number" property="number"/>
</collection>
</resultMap>
</mapper>
- 在核心配置文件
mybatis-config.xml
中,引入UsersMapper.xml
,代码如下:
<mapper resource="com/itheima/mapper/UsersMapper.xml"/>
- 在测试类
MyBatisTest
中编写测试方法findUserTest()
,代码如下:
/**
* 一对多
*/
@Test
public void findUserTest() {
// 1.通过工具类获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 查询id为1的用户信息
Users users = session.selectOne("com.itheima.mapper.UsersMapper.findUserWithOrders", 1);
// 3.输出查询信息
System.out.println(users);
// 4.关闭SqlSession
session.close();
}
多对多查询
以订单和商品为例,一个订单可以包含多种商品,而一个商品又可以属于多个订单,订单和商品就属于多对多的关联关系。
在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表的订单id作为外键关联订单表id,中间表中的商品id作为外键关联商品表的id。
下面以订单表和商品表之间的多对多关系为例来讲解如何使用MyBatis处理多对多关系,具体实现步骤如下:
- 在
mybatis
数据库中创建tb_product
商品表和tb_ordersitem
中间表,同时插入几条数据,代码如下:
## 在mybatis数据库中创建tb_product商品表和tb_ordersitem中间表,同时插入几条数据
USE mybatis;
# 创建一个名称为tb_product的表
CREATE TABLE tb_product (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) ,
price DOUBLE
);
# 插入三条数据
INSERT INTO tb_product VALUES ('1', 'Java基础入门', '44.5');
INSERT INTO tb_product VALUES ('2', 'JavaWeb程序入门', '38.5');
INSERT INTO tb_product VALUES ('3', 'SSM框架整合实践入门', '50.0');
# 创建一个名称为tb_ordersitem的表
CREATE TABLE tb_ordersitem (
id INT 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');
- 在
com.itheima.pojo
包下创建一个Product
类,封装商品属性,代码如下:
package com.itheima.pojo;
import java.util.List;
/**
* 商品持久化类
* @author Zhang
*/
public class Product {
private Integer id;
private String name;
private Double price;
private List<Orders> orders;
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", orders=" + orders +
'}';
}
public List<Orders> getOrders() {
return orders;
}
public void setOrders(List<Orders> orders) {
this.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;
}
}
- 在订单持久化类
Orders.java
中添加商品集合属性以及对应的getter/setter
、toString()
方法,代码如下:
private List<Product> productList;
@Override
public String toString() {
return "Orders{" +
"id=" + id +
", number='" + number + '\'' +
", productList=" + productList +
'}';
}
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
- 在
com.itheima.mapper
包下,创建订单实体映射文件OrdersMapper.xml
,用于编写订单信息查询的SQL语句,具体代码如下:
<?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映射语句来返回预期的特殊类型 -->
<select id="findOrdersWithProduct" parameterType="Integer" resultMap="OrdersWithProductResult">
SELECT *
FROM mybatis.tb_orders
WHERE id = #{id}
</select>
<resultMap id="OrdersWithProductResult" type="Orders">
<id property="id" column="id"/>
<result property="number" column="number"/>
<collection property="productList" column="id" ofType="Product" select="com.itheima.mapper.ProductMapper.findProductById"/>
</resultMap>
</mapper>
- 在
com.itheima.mapper
包下,创建订单实体映射文件ProductMapper.xml
,用于编写订单信息查询的SQL语句,具体代码如下:
<?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 mybatis.tb_product WHERE id IN (
SELECT tb_ordersitem.product_id FROM tb_ordersitem WHERE orders_id = #{id}
)
</select>
</mapper>
- 将新创建的映射文件
OrdersMapper.xml
和ProductMapper.xml
的文件路径配置到核心配置文件mybatis-config.xml
中,配置代码如下:
<mapper resource="com/itheima/mapper/ProductMapper.xml"/>
<mapper resource="com/itheima/mapper/OrdersMapper.xml"/>
- 在
MyBatisTest
中编写多对多关联查询的测试方法findOrdersTest()
,其代码如下:
/**
* 多对多
*/
@Test
public void findOrdersTest() {
// 1.通过工具类获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 查询id为1的订单信息
Orders orders = session.selectOne("com.itheima.mapper.OrdersMapper.findOrdersWithProduct", 1);
// 3.输出查询信息
System.out.println(orders);
// 4.关闭SqlSession
session.close();
}
这种方法来建立多对多映射查询过于麻烦,我们依然可以使用嵌套结果方法,代码如下:
- 在
OrdersMapper.xml
中添加多对多嵌套结果查询代码:
<!-- 多对多嵌套结果查询: 查询某订单及其关联的商品详情 -->
<select id="findOrdersWithProduct2" parameterType="Integer" resultMap="OrdersWithProductResult2">
SELECT o.*, p.id AS pid, p.name, p.price
FROM mybatis.tb_orders o, mybatis.tb_product p , mybatis.tb_ordersitem oi
WHERE oi.orders_id = o.id AND oi.product_id = p.id AND o.id = #{id}
</select>
<!-- 自定义手动映射类型 -->
<resultMap id="OrdersWithProductResult2" type="Orders">
<id column="id" property="id"/>
<result column="number" property="number"/>
<collection property="productList" ofType="Product">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
- 编写测试方法
findOrdersTest2()
,代码如下:
/**
* 多对多-嵌套结果
*/
@Test
public void findOrdersTest2() {
// 1.通过工具类获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 2.查询id为1的订单信息
Orders orders = session.selectOne("com.itheima.mapper.OrdersMapper.findOrdersWithProduct2", 1);
// 3.输出查询信息
System.out.println(orders);
// 4.关闭SqlSession
session.close();
}
MyBatis的缓存机制
在实际业务开发中,通常对数据库的性能要求较高,MyBatis中通过缓存机制来提高数据库性能。
一级缓存
MyBatis的一级缓存是SqlSession级别的缓存。具体来说就是:如果同一个SqlSession对象多次执行同一个完全相同的SQL语句,在第一次完成操作后,MyBatis会将查询结果写入一级缓存,此后,如果程序没有执行插入、更新、删除等操作,第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不再去数据库中操作,从而提高数据库的查询效率。
下面通过一个案例对MyBatis一级缓存的应用进行解释,该案例要求根据图书id查询图书信息:
- 在
mybatis
数据库创建名称为tb_book
的数据表,同时预先插入几条测试数据。代码如下:
## 创建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, '传智播客高教产品研发部'),
('Java基础案例教程', 48.0, '黑马程序员'),
('JavaWeb程序设计任务教程', 50.0, '黑马程序员');
- 在项目
com.itheima.pojo
包下创建持久化类Book
,在Book
类中定义相对应的属性和getter/setter
方法及toString()
方法,代码如下:
package com.itheima.pojo;
/**
* 在项目的com.itheima.pojo包下创建持久化类Book,在Book类中定义对应的属性,相应的getter/setter、toString()方法,代码如下:
* 图书持久化类
* @author Zhang
*/
public class Book {
// 图书id
private Integer id;
// 图书名称
private String bookName;
// 图书价格
private Double price;
// 图书作者
private String author;
@Override
public String toString() {
return "Book{" +
"id=" + id +
", bookName='" + bookName + '\'' +
", price=" + price +
", author='" + author + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
- 在
com.itheima.mapper
包中,创建图书映射文件BookMapper.xml
,并在该文件中编写图书id查询图书的SQL语句,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 在com.itheima.mapper中创建图书映射文件BookMapper.xml,并在该文件中编写根据id查询语句 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.BookMapper">
<!-- 根据id查询图书信息 -->
<select id="findBookById" parameterType="Integer" resultType="com.itheima.pojo.Book">
SELECT *
FROM mybatis.tb_book
WHERE id = #{id}
</select>
</mapper>
- 在核心配置文件
mybatis-config.xml
中的<mappers>
元素下,引入BookMapper.xml
映射文件,代码如下:
<mapper resource="com/itheima/mapper/BookMapper.xml"/>
- 由于需要通过
log4j
日志组件查看一级缓存的工作状态,所以需要在maven依赖仓库pom.xml
文件中引入log4j
的相关依赖,具体代码如下:
<!-- 配置log4j日志包依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 在
src/main/resources
目录下创建log4j.properties
文件,用于配置MyBatis和控制台的日志。代码如下:
# Global Logging configuration
log4j.rootLogger=DEBUG, Console
# Console output configuration
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
# Log output level
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 在测试类
MyBatisTest
中编写测试方法findBookByIdTest1()
方法,具体代码如下:
/**
* 根据id查询图书信息
*/
@Test
public void findBookByIdTest1() {
// 1.通过工具类获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 2.根据id查询图书信息
Book book1 = session.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
// 3.输出查询信息
System.out.println(book1.toString());
// 2.根据id查询图书信息
Book book2 = session.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
// 3.输出查询信息
System.out.println(book2.toString());
// 4.关闭SqlSession
session.close();
}
执行结果如下。在执行第二次查询时除了查询结果没有再输出任何信息,说明已经通过MyBatis的一级缓存存下了。:
2024-09-24 20:03:15,516 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==> Preparing: SELECT * FROM mybatis.tb_book WHERE id = ?
2024-09-24 20:03:15,540 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==> Parameters: 1(Integer)
2024-09-24 20:03:15,555 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - <== Total: 1
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}
二级缓存
由一级缓存的内容可知,相同的Mapper
类使用相同的SQL语句,如果SqlSession
不同,则如果两个SqlSession
查询数据库时,会查询数据库两次,这样也会降低数据库的查询效率。为了解决这个问题,就需要用到MyBaits的二级缓存。
MyBatis的二级缓存是Mapper级别的缓存,与一级缓存相比,二级缓存的范围更大,多个SqlSession
可以共用二级缓存,并且二级缓存可以自定义缓存资源。
在 MyBatis中,一个Mapper.xml
文件通常被称为一个Mapper
,MyBatis以namespace
区分 Mapper
,如果多个SqISession
对象使用同一个 Mapper 的相同査询语句去操作数据库,在第一个SqlSession
对象执行完后,MyBatis会将查询结果写人二级缓存,此后,如果程序没有执行插人、更新、删除操作,当第二个SqlSession
对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。MyBatis二级缓存的执行过程如下。
开启二级缓存步骤如下:
- 开启二级缓存的全局配置,在核心配置文件
mybatis-config.xml
中<settings>
中添加并启用二级缓存全局配置,代码如下:
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
- 修改映射文件
BookMapper.xml
,在映射文件的<mapper>
元素下追加编写<cache>
元素开启当前namespace
的二级缓存,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 在com.itheima.mapper中创建图书映射文件BookMapper.xml,并在该文件中编写根据id查询语句 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.BookMapper">
<cache/>
<!-- 根据id查询图书信息 -->
<select id="findBookById" parameterType="Integer" resultType="com.itheima.pojo.Book">
SELECT *
FROM mybatis.tb_book
WHERE id = #{id}
</select>
</mapper>
- **(先这么干,别问!)给
Book.java
持久化类实现可序列化接口
package com.itheima.pojo;
import java.io.Serializable;
/**
* 在项目的com.itheima.pojo包下创建持久化类Book,在Book类中定义对应的属性,相应的getter/setter、toString()方法,代码如下:
* 图书持久化类
* @author Zhang
*/
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
// 图书id
private Integer id;
// 图书名称
private String bookName;
// 图书价格
private Double price;
// 图书作者
private String author;
@Override
public String toString() {
return "Book{" +
"id=" + id +
", bookName='" + bookName + '\'' +
", price=" + price +
", author='" + author + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
- 在测试类
MyBatisTest
中添加测试方法findBookByIdTest1()
,代码如下:
/**
* 根据id查询图书信息
*/
@Test
public void findBookByIdTest1() {
// 1.通过工具类获取SqlSession对象
SqlSession session1 = MyBatisUtils.getSession();
SqlSession session2 = MyBatisUtils.getSession();
// 2.根据id查询图书信息
Book book1 = session1.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
// 3.输出查询信息
System.out.println(book1.toString());
// 4.关闭SqlSession
session1.close();
// 5.根据id查询图书信息
Book book2 = session2.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
// 6.输出查询信息
System.out.println(book2.toString());
// 7.关闭SqlSession
session2.close();
}
执行结果应该是这样的:
2024-09-24 20:44:56,021 [main] DEBUG [com.itheima.mapper.BookMapper] - Cache Hit Ratio [com.itheima.mapper.BookMapper]: 0.0
2024-09-24 20:44:56,166 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==> Preparing: SELECT * FROM mybatis.tb_book WHERE id = ?
2024-09-24 20:44:56,194 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==> Parameters: 1(Integer)
2024-09-24 20:44:56,210 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - <== Total: 1
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}
2024-09-24 20:44:56,215 [main] WARN [org.apache.ibatis.io.SerialFilterChecker] - As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
2024-09-24 20:44:56,220 [main] DEBUG [com.itheima.mapper.BookMapper] - Cache Hit Ratio [com.itheima.mapper.BookMapper]: 0.5
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}
Cache Hit Ratio(缓存命中率)
缓存命中率(Cache Hit Ratio)是衡量缓存系统效率的指标。它表示请求的数据有多少直接从缓存中获得,而不需要访问更慢的外部存储。计算方法是用缓存命中的次数除以总请求次数,然后乘以100%,得到百分比。
简单来说:
- 缓存命中:数据已经在缓存中,能快速提供。
- 缓存未命中:缓存中没有数据,需要从外部存储获取,比较耗时。
缓存命中率越高,系统性能越好,因为减少了从外部存储读取数据的时间。
案例:商品的类别
现有一个商品表product和一个商品类别表category,其中,商品类别表category和商品表product是一对多的关系。商品表product和商品表category分别如下标所示:
商品编号(id) | 商品名称(goodsname) | 商品单价(price) | 商品类别(type) |
---|---|---|---|
1 | 电视机 | 5000 | 1 |
2 | 冰箱 | 4000 | 2 |
3 | 空调 | 3000 | 2 |
4 | 洗衣机 | 2000 | 2 |
商品类别编号(id) | 商品类别名称(typename) |
---|---|
1 | 黑色家电 |
2 | 白色家电 |
本案例具体要求如下:根据以上两表在数据库分别创建商品表product和一个商品类别表category,并通过MyBatis查询商品类别为白色家电的商品所有信息。
代码实现
- 首先创建商品表
product
和一个商品类别表category
,并插入如上表的数据:
# 创建商品类别表product
CREATE TABLE product (
id INT PRIMARY KEY AUTO_INCREMENT,
goodsname VARCHAR(32),
price DOUBLE,
type INT
);
# 创建商品表category
CREATE TABLE category (
id INT PRIMARY KEY AUTO_INCREMENT,
typename VARCHAR(32)
);
# product表中插入数据
INSERT INTO product VALUES (1, '电视机', 5000, 1),
(2, '冰箱', 4000, 2),
(3, '空调', 3000, 2),
(4, '洗衣机', 2000, 2);
# category表中插入数据
INSERT INTO category VALUES (1, '黑色家电'),
(2, '白色家电');
- 写一下这两个数据表的实体类,并添加
setter/getter
方法及toString()
方法:
Product02.java
package com.itheima.pojo;
/**
* @author Zhang
*/
public class Product02 {
private Integer id;
private String goodsname;
private Double price;
private Integer type;
@Override
public String toString() {
return "Product02{" +
"id=" + id +
", goodsname='" + goodsname + '\'' +
", price=" + price +
", type=" + type +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getGoodsname() {
return goodsname;
}
public void setGoodsname(String goodsname) {
this.goodsname = goodsname;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
}
Category.java
package com.itheima.pojo;
/**
* @author Zhang
*/
public class Category {
private Integer id;
private String typename;
@Override
public String toString() {
return "Category{" +
"id=" + id +
", typename='" + typename + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTypename() {
return typename;
}
public void setTypename(String typename) {
this.typename = typename;
}
}
- 在
MybatisTest.java
中写一下测试方法,注意这里要获取多个值,所以选择selectList
方法,并提前将Product02
列表创建好:
@Test
public void findWhiteTest() {
SqlSession session = MyBatisUtils.getSession();
List<Product02> products = session.selectList("com.itheima.mapper.Product02Mapper.findWhite");
for (Product02 product : products) {
System.out.println(product);
}
session.close();
}
- 然后我们去写一下查询的
sql
语句,创建一个Product02Mapper
,并在其中创建一个findWhite
方法:
<?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.Product02Mapper">
<select id="findWhite" parameterType="com.itheima.pojo.Product02" resultType="com.itheima.pojo.Product02">
SELECT *
FROM mybatis.product p2, mybatis.category c
WHERE p2.type = c.id
AND c.typename = '白色家电'
</select>
</mapper>
- 测试之前不要忘记在核心配置文件中添加
mapper
索引,我们进入mybatis-config.xml
:
<mappers>
<mapper resource="mapper/UserMapper.xml" />
<mapper resource="com/itheima/mapper/StudentMapper.xml"/>
<mapper resource="com/itheima/mapper/EmployeeMapper.xml"/>
<mapper resource="com/itheima/mapper/CustomerMapper.xml"/>
<mapper resource="com/itheima/mapper/IdCardMapper.xml"/>
<mapper resource="com/itheima/mapper/PersonMapper.xml"/>
<mapper resource="com/itheima/mapper/UsersMapper.xml"/>
<mapper resource="com/itheima/mapper/ProductMapper.xml"/>
<mapper resource="com/itheima/mapper/OrdersMapper.xml"/>
<mapper resource="com/itheima/mapper/BookMapper.xml"/>
<mapper resource="com/itheima/mapper/Product02Mapper.xml"/>
</mappers>
- 回到
MybatisTest.java
测试类中,对mapper中定义的findWhite()
方法进行测试:
2024-10-08 21:43:49,416 [main] DEBUG [com.itheima.mapper.Product02Mapper.findWhite] - ==> Preparing: SELECT * FROM mybatis.product p2, mybatis.category c WHERE p2.type = c.id AND c.typename = '白色家电'
2024-10-08 21:43:49,444 [main] DEBUG [com.itheima.mapper.Product02Mapper.findWhite] - ==> Parameters:
2024-10-08 21:43:49,459 [main] DEBUG [com.itheima.mapper.Product02Mapper.findWhite] - <== Total: 3
Product02{id=2, goodsname='冰箱', price=4000.0, type=2}
Product02{id=3, goodsname='空调', price=3000.0, type=2}
Product02{id=4, goodsname='洗衣机', price=2000.0, type=2}
输出结果应该如上!
本章小结
本章内容主要设计数据表之间,以及对象之间的关联关系,由此引出了MyBatis框架中对关联关系的处理;通过一系列案例讲述了MyBatis在处理实体对象之间的3种关联关系。最后又说明了MyBatis的缓存机制,这一点十分重要,在面试中常常是面试官的心头好!需要清楚并能表述出MyBatis的一级缓存和二级缓存。希望您能通过对本章的学习,了解数据表之间及对象之间的3种关联关系,熟练使用MyBatis的缓存机制,并且能够在MyBatis框架中熟练运用3种关联关系进行查询,熟练配置MyBatis缓存,从而提高项目的开发效率。
原文地址:https://blog.csdn.net/HLJ_Student/article/details/142770783
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!