【Java_EE】Day04 MyBatis的关联映射和缓存机制

embedded/2024/10/9 8:43:41/

MyBatis的关联映射和缓存机制

一对一查询

主键数据表中的一条记录最多可以与另一个数据表的一条数据相关;例如一个人只能有一个身份证,同时一个身份证也只对应一个人。

在MyBatis中,通过<association>元素来处理一对一关联关系。<association>元素提供了一系列属性用于维护数据表之间的关系。属性如下:

属性说明
property用于指定映射到实体类对象的属性,要求与表字段一一对应
column用于指定表中对应的字段
javaType用于指定映射到实体对象的属性的类型
jdbcType用于指定数据表中对应字段的类型
fetchType用于指定在关联查询时是否启用延时加载,包含值:lazy(懒汉)和eager(饿汉),默认值为lazy(懒汉)(默认关联映射延迟加载)
select用于指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询
autoMapping用于指定是否自动映射
typeHandler用于指定一个类型处理器

<association>元素是<resultMap>的子元素,他有两种配置方式,分别是:嵌套查询和嵌套结果。

嵌套查询方式
  1. 先准备一下数据,在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);
  1. 创建POJO对象,分别在com.itheima.pojo包下创建IdCard.java类及Person.java类:

IdCard.java

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;}@Overridepublic String toString() {return "IdCard{" +"id=" + id +", code='" + code + '\'' +'}';}
}

Person.java

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;@Overridepublic 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;}
}
  1. com.itheima.mapper包中创建“身份证映射文件”IdCardMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。IdCardMapper.xml具体代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 映射文件约束信息 -->
<!DOCTYPE mapperPUBLIC "-//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, CODEFROM mybatis.tb_idcardWHERE id = #{id}</select>
</mapper>
  1. com.itheima.mapper包中创建“人员映射文件”PersonMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。PersonMapper.xml具体代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 映射文件约束信息 -->
<!DOCTYPE mapperPUBLIC "-//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_personWHERE 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>
  1. 在核心配置文件mybatis-config.xml中,引入IdCardMapper.xml和PersonMapper.xml映射文件,并为com.itheima.pojo包下的所有实体类定义别名,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//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>
  1. 在测试类MyBatisTest.java中,编写方法findPersonByIdTest(),具体代码如下:
java">    // 嵌套查询@Testpublic 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.关闭SqlSessionsession.close();}

但是MyBatis嵌套查询的方式要执行多条SQL语句,对于大型数据库集合和列表展示来说,这样可能会导致成百上千的SQL语句被执行,从而极大小号数据库性能并且会降低查询效率,这并不是开发人员期望的,接下来,我们通过Mybatis提供的嵌套结果方式进行关联查询:

  1. com.itheima.mapper包中PersonMapper.xml增加findPersonById2查询,并在映射文件中编写一对一关联映射查询的配置信息。PersonMapper.xml具体代码如下:
<!--    嵌套结果-使用嵌套结果映射来处理重复的联合结果的子集--><select id="findPersonById2" parameterType="Integer" resultMap="IdCardWithPersonResult2">SELECT p.*, idcard.CODEFROM mybatis.tb_person p, mybatis.tb_idcard idcardWHERE p.card_id = idcard.idAND 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>
  1. 在测试类MyBatisTest.java中,编写方法findPersonByIdTest2(),具体代码如下:
java">    // 嵌套结果@Testpublic 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.关闭SqlSessionsession.close();}

MyBatis延迟加载

MyBatis延迟加载是指不需要在每个映射文件中单独配置延迟加载。具体来说,MyBatis延迟加载主要与一对一(association)和一对多(collection)的关联关系相关。默认情况下,MyBatis会立即加载所有关联的对象,但开启延迟加载后,这些关联对象仅在真正使用时才被加载。其目的是为了使MyBatis在一定程度上降低运行消耗并提高查询效率。

配置方式如下:

  1. 在核心配置文件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中处理一对多关联关系,具体步骤如下:

  1. 先在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');
  1. com.itheima.pojo包中,创建持久化类Orders,并定义属性,代码如下:
java">package com.itheima.pojo;/*** 订单持久化类* @author Zhang*/
public class Orders {// 订单idprivate Integer id;// 订单编号private String number;@Overridepublic 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;}
}
  1. com.itheima.pojo包下创建持久化类Users,并定义数据表中对应的所有属性,代码如下:
java">package com.itheima.pojo;import java.util.List;/*** 用户持久化类* @author Zhang*/
public class Users {// 用户idprivate Integer id;// 用户名private String username;// 地址private String address;// 用户关联订单private List<Orders> ordersList;@Overridepublic 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;}
}
  1. com.itheima.mapper包下,创建用户实体映射文件UsersMapper.xml,并在文件中编写一对多关联映射查询的配置,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 映射文件约束信息 -->
<!DOCTYPE mapperPUBLIC "-//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.numberFROM mybatis.tb_user u , mybatis.tb_orders oWHERE u.id = o.user_idAND u.id = #{id}</select>
<!--    一对多:查看某用户及其关联的订单信息注意:当关联查询出的列名相同时,要使用别名进行区分--><resultMap id="UserWithOrdersResult" type="Users"><id column="id" property="id"/><result column="username" property="username"/><result column="address" property="address"/>
<!--        一对多关联映射:collectionofType:表示属性集合中元素的类型List<Orders>属性即Orders类        --><collection property="ordersList" ofType="Orders"><id column="orders_id" property="id"/><result column="number" property="number"/></collection></resultMap>
</mapper>
  1. 在核心配置文件mybatis-config.xml中,引入UsersMapper.xml,代码如下:
<mapper resource="com/itheima/mapper/UsersMapper.xml"/>
  1. 在测试类MyBatisTest中编写测试方法findUserTest(),代码如下:
java">    /*** 一对多*/@Testpublic 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.关闭SqlSessionsession.close();}

多对多查询

以订单和商品为例,一个订单可以包含多种商品,而一个商品又可以属于多个订单,订单和商品就属于多对多的关联关系。

在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表的订单id作为外键关联订单表id,中间表中的商品id作为外键关联商品表的id。

下面以订单表和商品表之间的多对多关系为例来讲解如何使用MyBatis处理多对多关系,具体实现步骤如下:

  1. 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');
  1. com.itheima.pojo 包下创建一个Product类,封装商品属性,代码如下:
java">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;@Overridepublic 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;}
}
  1. 在订单持久化类Orders.java中添加商品集合属性以及对应的getter/settertoString()方法,代码如下:
java">private List<Product> productList;@Overridepublic 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;}
  1. com.itheima.mapper包下,创建订单实体映射文件OrdersMapper.xml,用于编写订单信息查询的SQL语句,具体代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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_ordersWHERE 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>
  1. com.itheima.mapper包下,创建订单实体映射文件ProductMapper.xml,用于编写订单信息查询的SQL语句,具体代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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>
  1. 将新创建的映射文件OrdersMapper.xmlProductMapper.xml的文件路径配置到核心配置文件mybatis-config.xml中,配置代码如下:
        <mapper resource="com/itheima/mapper/ProductMapper.xml"/><mapper resource="com/itheima/mapper/OrdersMapper.xml"/>
  1. MyBatisTest中编写多对多关联查询的测试方法findOrdersTest(),其代码如下:
java">    /*** 多对多*/@Testpublic 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.关闭SqlSessionsession.close();}

这种方法来建立多对多映射查询过于麻烦,我们依然可以使用嵌套结果方法,代码如下:

  1. OrdersMapper.xml中添加多对多嵌套结果查询代码:
    <!--    多对多嵌套结果查询: 查询某订单及其关联的商品详情 --><select id="findOrdersWithProduct2" parameterType="Integer" resultMap="OrdersWithProductResult2">SELECT o.*, p.id AS pid, p.name, p.priceFROM mybatis.tb_orders o, mybatis.tb_product p , mybatis.tb_ordersitem oiWHERE 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>
  1. 编写测试方法findOrdersTest2(),代码如下:
java">    /*** 多对多-嵌套结果*/@Testpublic 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.关闭SqlSessionsession.close();}

MyBatis的缓存机制

在实际业务开发中,通常对数据库的性能要求较高,MyBatis中通过缓存机制来提高数据库性能。

一级缓存

MyBatis的一级缓存是SqlSession级别的缓存。具体来说就是:如果同一个SqlSession对象多次执行同一个完全相同的SQL语句,在第一次完成操作后,MyBatis会将查询结果写入一级缓存,此后,如果程序没有执行插入、更新、删除等操作,第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不再去数据库中操作,从而提高数据库的查询效率。

下面通过一个案例对MyBatis一级缓存的应用进行解释,该案例要求根据图书id查询图书信息:

  1. 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, '黑马程序员');
  1. 在项目com.itheima.pojo包下创建持久化类Book,在Book类中定义相对应的属性和getter/setter方法及toString()方法,代码如下:
java">package com.itheima.pojo;/*** 在项目的com.itheima.pojo包下创建持久化类Book,在Book类中定义对应的属性,相应的getter/setter、toString()方法,代码如下:* 图书持久化类* @author Zhang*/
public class Book {// 图书idprivate Integer id;// 图书名称private String bookName;// 图书价格private Double price;// 图书作者private String author;@Overridepublic 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;}
}
  1. com.itheima.mapper包中,创建图书映射文件BookMapper.xml,并在该文件中编写图书id查询图书的SQL语句,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 在com.itheima.mapper中创建图书映射文件BookMapper.xml,并在该文件中编写根据id查询语句 -->
<!DOCTYPE mapperPUBLIC "-//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_bookWHERE id = #{id}</select>
</mapper>
  1. 在核心配置文件mybatis-config.xml中的<mappers>元素下,引入BookMapper.xml映射文件,代码如下:
        <mapper resource="com/itheima/mapper/BookMapper.xml"/>
  1. 由于需要通过log4j日志组件查看一级缓存的工作状态,所以需要在maven依赖仓库pom.xml文件中引入log4j的相关依赖,具体代码如下:
        <!-- 配置log4j日志包依赖 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
  1. 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
  1. 在测试类MyBatisTest中编写测试方法findBookByIdTest1()方法,具体代码如下:
java">    /*** 根据id查询图书信息*/@Testpublic 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.关闭SqlSessionsession.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二级缓存的执行过程如下。

开启二级缓存步骤如下:

  1. 开启二级缓存的全局配置,在核心配置文件mybatis-config.xml<settings>中添加并启用二级缓存全局配置,代码如下:
		<!-- 开启二级缓存 --><setting name="cacheEnabled" value="true"/>
  1. 修改映射文件BookMapper.xml,在映射文件的<mapper>元素下追加编写<cache>元素开启当前namespace的二级缓存,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 在com.itheima.mapper中创建图书映射文件BookMapper.xml,并在该文件中编写根据id查询语句 -->
<!DOCTYPE mapperPUBLIC "-//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_bookWHERE id = #{id}</select>
</mapper>
  1. **(先这么干,别问!)Book.java持久化类实现可序列化接口
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;// 图书idprivate Integer id;// 图书名称private String bookName;// 图书价格private Double price;// 图书作者private String author;@Overridepublic 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;}
}
  1. 在测试类MyBatisTest中添加测试方法findBookByIdTest1(),代码如下:
java">	/*** 根据id查询图书信息*/@Testpublic 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.关闭SqlSessionsession1.close();// 5.根据id查询图书信息Book book2 = session2.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);// 6.输出查询信息System.out.println(book2.toString());// 7.关闭SqlSessionsession2.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电视机50001
2冰箱40002
3空调30002
4洗衣机20002
商品类别编号(id)商品类别名称(typename)
1黑色家电
2白色家电

本案例具体要求如下:根据以上两表在数据库分别创建商品表product和一个商品类别表category,并通过MyBatis查询商品类别为白色家电的商品所有信息。

代码实现

  1. 首先创建商品表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, '白色家电');
  1. 写一下这两个数据表的实体类,并添加setter/getter方法及toString()方法:

Product02.java

java">package com.itheima.pojo;/*** @author Zhang*/
public class Product02 {private Integer id;private String goodsname;private Double price;private Integer type;@Overridepublic 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

java">package com.itheima.pojo;/*** @author Zhang*/
public class Category {private Integer id;private String typename;@Overridepublic 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;}
}
  1. MybatisTest.java中写一下测试方法,注意这里要获取多个值,所以选择selectList方法,并提前将Product02列表创建好:
java">    @Testpublic 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();}
  1. 然后我们去写一下查询的sql语句,创建一个Product02Mapper,并在其中创建一个findWhite方法:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 映射文件约束信息 -->
<!DOCTYPE mapperPUBLIC "-//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 cWHERE p2.type = c.idAND c.typename = '白色家电'</select>
</mapper>
  1. 测试之前不要忘记在核心配置文件中添加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>
  1. 回到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缓存,从而提高项目的开发效率。


http://www.ppmy.cn/embedded/124980.html

相关文章

Unity 快速定位到目标文件夹

主要给习惯垂直布局用的&#xff0c;文件多了滚动都要滚半天 放到Editor下面&#xff0c;快捷键alt 文件夹首字母 public class EditorTool {//AltP打开资源路径[MenuItem("快捷方式/定位到预制体")]static void OpenResourcesUIPanel(){Selection.activeObject A…

类与对象、封装、继承、多态

文章目录 一、类与对象什么是对象什么是类什么是面向对象如何定义类如何new对象 二、封装三、继承多态 五、总结 一、类与对象 什么是对象 对象&#xff1a;是一个一个实实在在的&#xff0c;具体的个体&#xff08;实体&#xff09;。比如&#xff1a;一个个人、一辆辆车、一…

降重秘籍:如何利用ChatGPT将重复率从45%降至10%以下?

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 重复率高达45%&#xff1f;很多人一查论文的重复率&#xff0c;瞬间想“完了&#xff0c;这次真的要重写了”。但其实不用这么绝望&#xff01;有了ChatGPT&#xff0c;降重真的没那么难。今天就教你几招&a…

测试用例的编写

1.基本概念&#xff1a; 编写测试用例是确保代码质量和正确性的重要环节&#xff0c;尤其是在软件开发和维护过程中。测试用例通常用于验证功能是否符合预期&#xff0c;并及时发现潜在的错误或漏洞。 2.常见的测试用例编写方法&#xff1a; 等价划分法&#xff0c;边界值法&a…

【智能算法应用】人工水母搜索算法求解二维路径规划问题

摘要 本文应用人工水母搜索算法&#xff08;Jellyfish Search, JFS&#xff09;求解二维空间中的路径规划问题。水母搜索算法是一种新型的智能优化算法&#xff0c;灵感来源于水母的群体运动行为&#xff0c;通过模仿水母的觅食、漂浮等行为&#xff0c;实现全局最优路径的搜索…

探索循环神经网络RNN:解锁序列数据的奥秘

在这个数据驱动的时代&#xff0c;机器学习模型已经深入到我们生活的方方面面&#xff0c;从智能推荐系统到自然语言处理&#xff0c;无一不彰显其强大的能力。在众多模型中&#xff0c;循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;以其独特的结构和对序…

Python知识点:如何使用Raspberry Pi与Python进行边缘计算

开篇&#xff0c;先说一个好消息&#xff0c;截止到2025年1月1日前&#xff0c;翻到文末找到我&#xff0c;赠送定制版的开题报告和任务书&#xff0c;先到先得&#xff01;过期不候&#xff01; 如何使用Raspberry Pi与Python进行边缘计算 Raspberry Pi是一款广受欢迎的小型单…

pycharm生成的exe执行后报错

元素 application 显示为元素 urn:schemas-microsoft-com:asm.v1^dependentAssembly (此版本的 Windows 不支持)的子元素。 日志名称: Application 来源: SideBySide 日期: 2024/10/8 14:14:12 事件 ID: 72 任务类别: 无 级别…