学习目标:
- 了解数据表之间的三种关联关系
- 了解对象之间的三种关系
- 熟悉关联关系中的嵌套查询和嵌套结果
- 掌握一对一关联映射
- 掌握—对多关联映射
- 掌握多对多关联映射
- 熟悉Mybatis的缓存机制
文章概述:
前面几章介绍了MyBatis的基本用法、关联映射和动态SQL等重要知识,但这些知识只是针对单表实现进行操作的,在实际开发中,对数据库的操作常常会涉及到多张表,针对多表之间的操作,MyBatis提供了关联映射,通过关联映射可以很好地处理表与表、对象与对象之间的关联关系。此外,在实际开发中经常需要合理地利用MyBatis缓存来加快数据库查询,进而有效地提升数据库性能。本章将对MyBatis的关联映射以及MyBatis缓存机制进行详细讲解。
关联映射关系
数据库中表和表的关系:
- 一对—关系: 
  - 一个数据表中的一条记录最多可以和另一个数据表中的一条记录相关。例如现实生活中学生与校园卡就属于一对一的关系,一个学生只能拥有一张校园卡,一张校园卡只能属于一个学生。
 
- —对多关系: 
  - 主键数据表中的一条记录可以和另外一个数据表的多条记录相关。但另外一个数据表中的记录只能与主键数据表中的某一条记录相关。例如,现实中班级与学生的关系就属于一对多的关系,一个班级可以有很多学生,但一个学生只能属于一个班级。
 
- 多对多关系: 
  - 一个数据表中的一条记录可以与另外一个数据表任意数量的记录相关,另外一个数据表中的一条记录也可以与本数据表中任意数量的记录相关。例如,现实中学生与教师属于多对多的关系,一名学生可以由多名教师授课,一名教师可以为多名学生授课。
 
Java对象关联映射关系:
数据表之间的关系实质上描述的是数据之间的关系,除了数据表,在Java中,还可以通过对象来描述数据之间的关系。通过Java对象描述数据之间的关系,其实就是使对象的属性与另一个对象的属性相互关联。

一对一:
- 就是在本类中定义与之关联的类的对象作为属性,例如,A类中定义B类对象作为属性,在B类中定义A类对象a作为属性。
一对多:
- 就是一个A类对象对应多个B类对象的情况,例如,定义在A类中,定义一个类对象的集合作为A类的属性;在B类中,定义A类对象a作为B类的属性。
多对多:
- 在两个相互关联的类中,都可以定义多个与之关联的类的对象。例如,在A类中定义B类类型的集合作为属性,在B类中定义A类类型的集合作为属性。
一对一查询
<association>元素
在MyBatis中,通过<association>元素来处理一对一关联关系。<association>元素提供了一系列属性用于维护数据表之间的关系。
<association>元素属性

<association>元素的配置方式
强烈建议先看一下这个association元素用法
<association>元素是<resultMap>元素的子元素,它有两种配置方式,嵌套查询方式和嵌套结果方式,下面对这两种配置方式分别进行介绍。
- 嵌套查询方式(//如果第一张表有外键,column="外键列名"。//如果第一张表没有外键,column="javaType对应表中的主键名"
 
- 嵌套结果方式(不用关注column的值) 
代码示例:
1.引入依赖pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>_20230417</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.26</version>
        </dependency>
    </dependencies>
</project>2.配置数据库连接信息:(db.properties)
driver.driver=com.mysql.cj.jdbc.Driver
driver.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver.username=root
driver.password=6666663.配置slf4j日志打印
log4j.appender.a=org.apache.log4j.ConsoleAppender
log4j.appender.a.Target=System.out
log4j.appender.a.layout=org.apache.log4j.PatternLayout
log4j.appender.a.layout.ConversionPattern=%-d{HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
log4j.appender.b=org.apache.log4j.FileAppender
log4j.appender.b.File=f://travel.log
log4j.appender.b.layout=org.apache.log4j.PatternLayout
log4j.appender.b.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
log4j.rootLogger=debug,a,b3.配置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>
        <!--        打开延迟加载的开关-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--        将积极加载改为消息加载,即按需加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    <!-- 数据库连接相关配置 ,db.properties文件中的内容-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver.driver}"/>
                <property name="url" value="${driver.url}"/>
                <property name="username" value="${driver.username}"/>
                <property name="password" value="${driver.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="cn.hdc.mapper"/>
    </mappers>
</configuration>4.创建数据库,创建表(tb_idcard,tb_person)


5.根据数据库中的表创建实体类IdCard
package cn.hdc.pojo;
public class IdCard {
    private int id;
    private String code;
    @Override
    public String toString() {
        return "IdCard{" +
                "id=" + id +
                ", code='" + 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;
    }
}
6. 根据数据库中的表创建实体类Person
package cn.hdc.pojo;
public class Person {
    private int id;
    private String name;
    private int age;
    private String sex;
    private int card_id;
    private IdCard card;
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", card_id=" + card_id +
                ", card=" + card +
                '}';
    }
    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 int getCard_id() {
        return card_id;
    }
    public void setCard_id(int card_id) {
        this.card_id = card_id;
    }
    public IdCard getCard() {
        return card;
    }
    public void setCard(IdCard card) {
        this.card = card;
    }
}
7.创建工具类MyBatisUtils
package cn.hdc.utils;
 
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.Reader;
 
public class MybaitsUtils {
    private static SqlSessionFactory sqlSessionFactory = null;
 
    static {
        try {
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 
    public static SqlSession getSession() {
        return sqlSessionFactory.openSession();
    }
}8.创建接口IdCardMapper,声明要实现的方法
package cn.hdc.mapper;
import cn.hdc.pojo.IdCard;
import java.util.List;
public interface IdCardMapper {
    public IdCard findCodeById(Integer id);
}
9.创建接口PersonMapper,声明要实现的方法(findPersonById是嵌套查询方式,findPersonById2是嵌套结果方式)
package cn.hdc.mapper;
import cn.hdc.pojo.Person;
import java.util.List;
public interface PersonMapper {
    public Person findPersonById(Integer id);
    public Person findPersonById2(Integer id);
}
10.创建IdCardMapper.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="cn.hdc.mapper.IdCardMapper">
    <select id="findCodeById" parameterType="integer" resultType="cn.hdc.pojo.IdCard">
        select *
        from tb_idcard
        where id = #{id}
    </select>
</mapper>11.创建PersonMapper.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="cn.hdc.mapper.PersonMapper">
    <!--    第一种方式嵌套查询方式完成一对一结果配置-->
    <select id="findPersonById" parameterType="integer" resultMap="IdCardWithPersonResult">
        select *
        from tb_person
        where id = #{id}
    </select>
    <resultMap id="IdCardWithPersonResult" type="cn.hdc.pojo.Person">
        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="card_id" column="card_id"></result>
        <association property="card" column="card_id" javaType="cn.hdc.pojo.IdCard"
                     select="cn.hdc.mapper.IdCardMapper.findCodeById">
        </association>
    </resultMap>
    <!--    第二种方式:嵌套结果方式完成一对一结果配置-->
    <select id="findPersonById2" parameterType="integer" resultMap="IdCardWithPersonResult2">
        select *,
               p.id pid,
               c.id cid
        from tb_person p,
             tb_idcard c
        where p.card_id = c.id
          and p.id = #{id}
    </select>
    <resultMap id="IdCardWithPersonResult2" type="cn.hdc.pojo.Person">
        <id property="id" column="pid"></id>
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="card_id" column="card_id"></result>
        <association property="card" javaType="cn.hdc.pojo.IdCard">
            <id property="id" column="id"></id>
            <result property="code" column="code"></result>
        </association>
    </resultMap>
</mapper>12.编写测试类
package cn.hdc.mapper;
import cn.hdc.pojo.Person;
import cn.hdc.utils.MybaitsUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class PersonMapperTest {
    private SqlSession session;
    @Before
    public void setUp() throws Exception {
        this.session = MybaitsUtils.getSession();
    }
    @After
    public void tearDown() throws Exception {
        this.session.commit();
        this.session.close();
    }
    @Test
    public void findPersonById() {
        Person person = session.selectOne("cn.hdc.mapper.PersonMapper.findPersonById", 1);
        System.out.println(person.getName());
        System.out.println("需要使用另外一张表的数据");
        System.out.println(person.getCard());
        System.out.println(person);
    }
    @Test
    public void findPersonById2() {
        Person person = session.selectOne("cn.hdc.mapper.PersonMapper.findPersonById2", 1);
        System.out.println(person);
    }
}13.项目结构

14.findPersonById测试结果说明:

我们不使用懒加载,把settings注释掉

编写测试类以及运行结果:

明明没用到tb_idcard却执行了俩句sql,这就造成资源浪费,改进方案,配置懒加载

运行结果:

对比上边的结果:(只有用到另一张表,才会去执行另一张表的sql)

在使用MyBatis嵌套查询方式进行MyBatis关联映射查询时,使用MyBatis的延迟加载在一定程度上可以降低运行消耗并提高查询效率。MyBatis默认没有开启延迟加载,需要在mybatis-config.xml中的<settings>元素内进行配置。

15.findPersonById2测试结果说明:
虽然使用嵌套查询的方式比较简单,但是MyBatis嵌套查询的方式要执行多条SQL语句,这对于大型数据集合和列表展示不是很好,因为这样可能会导致成百上千条关联的SQL语句被执行,从而极大地消耗数据库性能并且会降低查询效率,这并不是开发人员所期望的。为此,我们可以使用MyBatis提供的嵌套结果方式进行关联查询。

运行结果:

一对多查询
<collection>元素
在MyBatis中,通过<collection>元素来处理一对多关联关系。<collection>元素的属性大部分与<association>元素相同,但具还包含一个特殊属性--ofType。ofType属性与javaType属性对应,它用于指定实体类对象中集合类属性所包含的元素的类型。
<collection>元素的配置方式
<collection>元素是<resultMap>元素的子元素,<collection >元素有嵌套查询和嵌套结果两种配置方式。
a.嵌套查询方式(和上边association标签的用法基本一致)

b.嵌套结果方式

代码示例:
1.创建表


2.根据表字段,编写实体类 Orders
package cn.hdc.pojo;
public class Orders {
    private int id;
    private String number;
    private int userId;
    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", number='" + number + '\'' +
                ", userId=" + userId +
                '}';
    }
    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 getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
}
3. 根据表字段,编写实体类User
package cn.hdc.pojo;
import java.util.List;
public class User {
    private int id;
    private String username;
    private String address;
    private List<Orders> ordersList;
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", ordersList=" + 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;
    }
}
4.根据实体类创建接口,声明要实现的方法OrdersMapper接口
package cn.hdc.mapper;
import cn.hdc.pojo.Orders;
import java.util.List;
public interface OrdersMapper {
    public Orders findOrdersById(Integer id);
}
5.根据实体类创建接口,声明要实现的方法UserMapper接口
package cn.hdc.mapper;
import cn.hdc.pojo.Orders;
import cn.hdc.pojo.User;
import java.util.List;
public interface UserMapper {
    public User findUserWithOrdersById(Integer id);
    public User findUserWithOrdersById2(Integer id);
}
6.使用嵌套结果方式查询,编写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="cn.hdc.mapper.UserMapper">
        <!--    1.嵌套结果方式  -->
    <select id="findUserWithOrdersById" parameterType="integer" resultMap="userWithOrdersResult">
        select *,
               u.id uid,
               o.id oid
        from tb_user u,
             tb_orders o
        where u.id = o.user_id
          and u.id = #{id}
    </select>
    <resultMap id="userWithOrdersResult" type="cn.hdc.pojo.User">
        <id property="id" column="uid"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <collection property="ordersList"
                    ofType="cn.hdc.pojo.Orders">
            <id property="id" column="oid"></id>
            <result property="number" column="number"></result>
            <result property="userId" column="user_id"></result>
        </collection>
    </resultMap>
</mapper>7.编写测试类:
package cn.hdc.mapper;
import cn.hdc.pojo.User;
import cn.hdc.utils.MybaitsUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class UserMapperTest {
    private SqlSession session;
    @Before
    public void setUp() throws Exception {
        this.session = MybaitsUtils.getSession();
    }
    @After
    public void tearDown() throws Exception {
        this.session.commit();
        this.session.close();
    }
    @Test
    public void findUserWithOrdersById() {
        User user = session.selectOne("cn.hdc.mapper.UserMapper.findUserWithOrdersById", 1);
        System.out.println(user);
    }
}8.运行结果:

9.使用嵌套查询的方式,编写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="cn.hdc.mapper.UserMapper">
    <!--    1.嵌套结果方式  -->
    <select id="findUserWithOrdersById" parameterType="integer" resultMap="userWithOrdersResult">
        select *,
               u.id uid,
               o.id oid
        from tb_user u,
             tb_orders o
        where u.id = o.user_id
          and u.id = #{id}
    </select>
    <resultMap id="userWithOrdersResult" type="cn.hdc.pojo.User">
        <id property="id" column="uid"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <collection property="ordersList"
                    ofType="cn.hdc.pojo.Orders">
            <id property="id" column="oid"></id>
            <result property="number" column="number"></result>
            <result property="userId" column="user_id"></result>
        </collection>
    </resultMap>
    <!--    2.嵌套查询方式  -->
    <select id="findUserWithOrdersById2" parameterType="integer" resultMap="userWithOrdersResult2">
        select *
        from tb_user
        where id = #{id}
    </select>
    <!--                    因为User实体类没有外键,所以column=‘ofType对应数据库表中的主键’-->
    <resultMap id="userWithOrdersResult2" type="cn.hdc.pojo.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <collection property="ordersList"
                    column="id"
                    ofType="cn.hdc.pojo.Orders"
                    select="cn.hdc.mapper.OrdersMapper.findOrdersById">
        </collection>
    </resultMap>
</mapper>10.编写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="cn.hdc.mapper.OrdersMapper">
    <select id="findOrdersById" resultMap="OrdersMap" parameterType="integer">
        select *
        from tb_orders
        where user_id = #{id}
    </select>
    <resultMap id="OrdersMap" type="cn.hdc.pojo.Orders">
        <id property="id" column="id"></id>
        <result property="number" column="number"></result>
        <result property="userId" column="user_id"></result>
    </resultMap>
</mapper>11.编写测试类:
package cn.hdc.mapper;
import cn.hdc.pojo.User;
import cn.hdc.utils.MybaitsUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class UserMapperTest {
    private SqlSession session;
    @Before
    public void setUp() throws Exception {
        this.session = MybaitsUtils.getSession();
    }
    @After
    public void tearDown() throws Exception {
        this.session.commit();
        this.session.close();
    }
    @Test
    public void findUserWithOrdersById() {
        User user = session.selectOne("cn.hdc.mapper.UserMapper.findUserWithOrdersById", 1);
        System.out.println(user);
    }
    @Test
    public void findUserWithOrdersById2() {
        User user = session.selectOne("cn.hdc.mapper.UserMapper.findUserWithOrdersById2", 1);
        System.out.println(user);
    }
}12.findUserWithOrdersById2运行结果:

多对多查询
订单和商品多对多关系图
在实际项目开发中,多对多的关联关系非常常见。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单,订单和商品属于多对多关联关系,订单和商品之间的关联关系如图。

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

案例:实现多对多查询
1.引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>_20230417</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.26</version>
        </dependency>
    </dependencies>
</project>2.创建数据库,创建表tb_product,tb_ordersitem,tb_orders
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 的中间表
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)
);
# 插入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');
4.根据表字段创建对应实体Orders
package cn.hdc.pojo;
import java.util.List;
public class Orders {
    private int id;
    private String number;
    private int userId;
    private List<Product> productList;
    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 + '\'' +
                ", userId=" + userId +
                ", productList=" + productList +
                '}';
    }
    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 getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
}
5.根据表字段创建对应实体Product
package cn.hdc.pojo;
import java.util.List;
public class Product {
    private int id;
    private String name;
    private double price;
    private List<Orders> ordersList;
    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", ordersList=" + ordersList +
                '}';
    }
    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;
    }
    public List<Orders> getOrdersList() {
        return ordersList;
    }
    public void setOrdersList(List<Orders> ordersList) {
        this.ordersList = ordersList;
    }
}
6.创建接口OrdersMapper,声明要实现的方法
package cn.hdc.mapper;
import cn.hdc.pojo.Orders;
import java.util.List;
public interface OrdersMapper {
    public Orders findOrdersById(Integer id);
    public Orders findOrdersById2(Integer id);
    public Orders findOrdersById3(Integer id);
}
7.创建接口ProductMapper,声明要实现的方法
package cn.hdc.mapper;
import cn.hdc.pojo.Product;
public interface ProductMapper {
    public Product findProductById(Integer id);
}
8.创建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="cn.hdc.mapper.OrdersMapper">
    <!--    嵌套查询方式-->
    <select id="findOrdersById2"
            parameterType="integer"
            resultMap="orderWithProductResults">
        select *
        from tb_orders
        where id = #{id}
    </select>
    <resultMap id="orderWithProductResults" type="cn.hdc.pojo.Orders">
        <id property="id" column="id"></id>
        <result property="number" column="number"></result>
        <collection property="productList"
                    javaType="list"
                    ofType="cn.hdc.pojo.Product"
                    column="id"
                    select="cn.hdc.mapper.ProductMapper.findProductById">
        </collection>
    </resultMap>
    <!--    嵌套结果方式-->
    <select id="findOrdersById3"
            parameterType="integer"
            resultMap="orderWithProductResults2">
        select o.*,
               p.*,
               o.id oid,
               p.id pid
        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 id="orderWithProductResults2" type="cn.hdc.pojo.Orders">
        <id property="id" column="oid"></id>
        <result property="number" column="number"></result>
        <collection property="productList"
                    javaType="list"
                    ofType="cn.hdc.pojo.Product">
            <id property="id" column="pid"></id>
            <result property="name" column="name"></result>
            <result property="price" column="price"></result>
        </collection>
    </resultMap>
    <select id="findOrdersById" resultMap="OrdersMap" parameterType="integer">
        select *
        from tb_orders
        where user_id = #{id}
    </select>
    <resultMap id="OrdersMap" type="cn.hdc.pojo.Orders">
        <id property="id" column="id"></id>
        <result property="number" column="number"></result>
        <result property="userId" column="user_id"></result>
    </resultMap>
</mapper>9.创建ProguctMapper.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="cn.hdc.mapper.ProductMapper">
    <select id="findProductById"
            parameterType="integer"
            resultType="cn.hdc.pojo.Product">
        select *
        from tb_product
        where id in
              (select product_id from tb_ordersitem where orders_id = #{id});
    </select>
</mapper>10.创建OderMapperTest测试类:
package cn.hdc.mapper;
import cn.hdc.pojo.Orders;
import cn.hdc.utils.MybaitsUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.*;
public class OrdersMapperTest {
    private SqlSession session;
    @Before
    public void setUp() throws Exception {
        this.session = MybaitsUtils.getSession();
    }
    @After
    public void tearDown() throws Exception {
        this.session.commit();
        this.session.close();
    }
    @Test
    public void findOrdersById() {
        List<Orders> list = session.selectList("cn.hdc.mapper.OrdersMapper.findOrdersById", 1);
        System.out.println(list);
    }
    @Test
    public void findOrdersById2() {
        Object order = session.selectOne("cn.hdc.mapper.OrdersMapper.findOrdersById2", 1);
        System.out.println(order);
    }
    @Test
    public void findOrdersById3() {
        Object order = session.selectOne("cn.hdc.mapper.OrdersMapper.findOrdersById3", 1);
        System.out.println(order);
    }
}11.findOrderById2运行结果(嵌套查询方式)

12.findOrderById3运行结果(嵌套结果查询方式)
 
 
13.项目结构:

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

查询过程:

代码示例:
1.创建数据库,创建表
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,'黑马程序员');2.根据表中字段创建实体Book
package cn.hdc.pojo;
public class Book {
    private int 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 int getId() {
        return id;
    }
    public void setId(int 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;
    }
}
3.创建BookMapper接口声明要实现的方法
package cn.hdc.mapper;
import cn.hdc.pojo.Book;
public interface BookMapper {
    public Book findBookById(Integer id);
    public Integer updateBook(Book book);
}
4.创建BookMapper.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="cn.hdc.mapper.BookMapper">
    <select id="findBookById" resultType="cn.hdc.pojo.Book">
        select * from tb_book where id = #{id}
    </select>
    
    <update id="updateBook" parameterType="cn.hdc.pojo.Book">
        update tb_book set bookName = #{bookName},price=#{price} where id=#{id}
    </update>
</mapper>5.db.properties
driver.driver=com.mysql.cj.jdbc.Driver
driver.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver.username=root
driver.password=6666666.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>
        <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。默认:true  -->
         <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性 . 默认:true-->
         <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    <!-- 数据库连接相关配置 ,db.properties文件中的内容-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver.driver}"/>
                <property name="url" value="${driver.url}"/>
                <property name="username" value="${driver.username}"/>
                <property name="password" value="${driver.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="cn.hdc.mapper"/>
    </mappers>
</configuration>7.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>_20230417</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.26</version>
        </dependency>
    </dependencies>
</project>8.创建单元测试
package cn.hdc.mapper;
import cn.hdc.pojo.Book;
import cn.hdc.utils.MybaitsUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class BookMapperTest {
    private SqlSession session;
    @Before
    public void setUp() throws Exception {
        this.session = MybaitsUtils.getSession();
    }
    @After
    public void tearDown() throws Exception {
        this.session.commit();
        this.session.close();
    }
    @Test
    public void findBookByIdTest1() {
        System.out.println("第一次查询");
        Book book = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book);
        System.out.println("第二次查询");
        Book book2 = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book2);
    }
    @Test
    public void findBookByIdTest2() {
        System.out.println("第一次查询");
        Book book = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book);
        //更新表
        Book updatebook = new Book();
        updatebook.setId(2);
        updatebook.setBookName("三国演义");
        session.update("cn.hdc.mapper.BookMapper.updateBook.updateBook", updatebook);
        System.out.println("第二次查询");
        Book book2 = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book2);
    }
}9.log4j.properties
#log4j.appender.a=org.apache.log4j.ConsoleAppender
#log4j.appender.a.Target=System.out
#log4j.appender.a.layout=org.apache.log4j.PatternLayout
#log4j.appender.a.layout.ConversionPattern=%-d{HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
#log4j.appender.b=org.apache.log4j.FileAppender
#log4j.appender.b.File=f://travel.log
#log4j.appender.b.layout=org.apache.log4j.PatternLayout
#log4j.appender.b.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
#log4j.rootLogger=debug,a,b
#全局日志配置
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=DEBUG10.项目结构

11.findBookByIdTest1 运行结果说明:

很明显看到sql只执行了一次,第二次从缓存里边拿。
12. findBookByIdTest2 运行结果说明:

数据库查了2次,中间有更新语句,所以不能从缓存中读取。
MyBatis如何防止程序误读
当程序对数据库执行了插入、更新、删除操作后,MyBatis会清空一级缓存中的内容,以防止程序误读。MyBatis一级缓存被清空之后,再次使用SQL查询语句访问数据库时,
 MyBatis会重新访问数据库。
二级缓存
通过学习一级缓存的内容可知,相同的Mapper类,相同的SQL语句,如果SqlSession不同,则两个SqlSession查询数据库时,会查询数据库两次,这样也会降低数据库的查询效率。为了解决这个问题,就需要用到MyBatis的二级缓存。MyBatis的二级缓存是Mapper级别的缓存,与一级缓存相比,二级缓存的范围更大,多个SqlSession可以共用二级缓存,并且二级缓存可以自定义缓存资源。
MyBatis二级缓存的执行过程
在MyBatis中,一个Mapper.xml文件通常称为一个Mapper,MyBatis以namespace区分Mapper,如果多个SqlSession对象使用同一个Mapper的相同查询语句去操作数据库,在第一个SqlSession对象执行完后,MyBatis会将查询结果写入二级缓存,此后,如果程序没有执行插入、更新、删除操作,当第二个SqlSession对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。

二级缓存与一级缓存的不同点
与MyBatis的一级缓存不同的是,MyBatis的二级缓存需要手动开启,开启二级缓存通常要完成以下两个步骤。
- 开启二级缓存的全局配置 
- 开启当前Mapper的namespace下的二级缓存  
默认状态的二级缓存可实现的功能

<cache>元素的属性

代码示例:
1.Book实体类必须实现Serializable 接口
package cn.hdc.pojo;
import java.io.Serializable;
public class Book implements Serializable {
    private int 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 int getId() {
        return id;
    }
    public void setId(int 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;
    }
}
2.BookMapper
package cn.hdc.mapper;
import cn.hdc.pojo.Book;
public interface BookMapper {
    public Book findBookById(Integer id);
    public Integer updateBook(Book book);
}
3.BookMapper.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="cn.hdc.mapper.BookMapper">
    <cache>
    </cache>
    <select id="findBookById" resultType="cn.hdc.pojo.Book">
        select *
        from tb_book
        where id = #{id}
    </select>
    <update id="updateBook" parameterType="cn.hdc.pojo.Book">
        update tb_book
        set bookName = #{bookName},
            price=#{price}
        where id = #{id}
    </update>
</mapper>4.mybatis-config中通过settings标签开启二级缓存
<?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>
        <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。默认:true  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性 . 默认:true-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--        开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <!-- 数据库连接相关配置 ,db.properties文件中的内容-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver.driver}"/>
                <property name="url" value="${driver.url}"/>
                <property name="username" value="${driver.username}"/>
                <property name="password" value="${driver.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="cn.hdc.mapper"/>
    </mappers>
</configuration>5.log4j.properties
#log4j.appender.a=org.apache.log4j.ConsoleAppender
#log4j.appender.a.Target=System.out
#log4j.appender.a.layout=org.apache.log4j.PatternLayout
#log4j.appender.a.layout.ConversionPattern=%-d{HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
#log4j.appender.b=org.apache.log4j.FileAppender
#log4j.appender.b.File=f://travel.log
#log4j.appender.b.layout=org.apache.log4j.PatternLayout
#log4j.appender.b.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
#log4j.rootLogger=debug,a,b
#全局日志配置
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=DEBUG6.生成测试类
package cn.hdc.mapper;
import cn.hdc.pojo.Book;
import cn.hdc.utils.MybaitsUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class BookMapperTest {
    private SqlSession session;
    @Before
    public void setUp() throws Exception {
        this.session = MybaitsUtils.getSession();
    }
    @After
    public void tearDown() throws Exception {
        this.session.commit();
        this.session.close();
    }
    @Test
    public void findBookByIdTest1() {
        System.out.println("第一次查询");
        Book book = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book);
        System.out.println("第二次查询");
        Book book2 = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book2);
    }
    @Test
    public void findBookByIdTest2() {
        System.out.println("第一次查询");
        Book book = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book);
        //更新表
        Book updatebook = new Book();
        updatebook.setId(2);
        updatebook.setBookName("三国演义");
        updatebook.setPrice(30);
        int ret = session.update("cn.hdc.mapper.BookMapper.updateBook", updatebook);
        if (ret > 0) {
            System.out.println("更新成功!");
        } else {
            System.out.println("更新失败!");
        }
        System.out.println("第二次查询");
        Book book2 = session.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book2);
    }
    @Test
    public void findBookByIdTest3() {
        SqlSession session1 = MybaitsUtils.getSession();
        SqlSession session2 = MybaitsUtils.getSession();
        System.out.println("第一次查询");
        Book book = session1.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book);
        session1.close();
        System.out.println("第二次查询");
        Book book2 = session2.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book2);
        session2.close();
    }
    @Test
    public void findBookByIdTest4() {
        SqlSession session1 = MybaitsUtils.getSession();
        SqlSession session2 = MybaitsUtils.getSession();
        SqlSession session3 = MybaitsUtils.getSession();
        System.out.println("第一次查询");
        Book book = session1.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book);
        session1.close();
        Book updatebook = new Book();
        updatebook.setId(2);
        updatebook.setBookName("三国演义");
        updatebook.setPrice(30);
        session3.update("cn.hdc.mapper.BookMapper.updateBook", updatebook);
        session3.commit();
        session3.close();
        System.out.println("第二次查询");
        Book book2 = session2.selectOne("cn.hdc.mapper.BookMapper.findBookById", 1);
        System.out.println(book2);
        session2.close();
    }
}7.findBookByIdTest3运行结果:

sql只执行了一次,第二次查询是从二级缓存中拿的数据。
8.findBookByIdTest4运行结果:

由于中间有更新语句,二级缓存失效。
缓存命中率
终端用户访问缓存时,如果在缓存中查找到了要被访问的数据,就叫做命中。如果缓存中没有查找到要被访问的数据,就是没有命中。当多次执行查询操作时,缓存命中次数与总的查询次数(缓存命中次数+缓存没有命中次数)的比,就叫作缓存命中率,即缓存命中率=缓存命中次数/总的查询次数。当MyBatis开启二级缓存后,第一次查询数据时,由于数据还没有进入缓存,所以需要在数据库中查询而不是在缓存中查询,此时,缓存命中率为0。第一次查询过后,MyBatis会将查询到的数据写入缓存中当第二次再查询相同的数据时,MyBatis会直接从缓存中获取这条数据,缓存将命中,此时的缓存命中率为0.5 ( 1/2)。当第三次查询相同的数据,则缓存命中率为0.66666 ( 2/3 ),以此类推。
案例,商品的类别
1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>_20230419_1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.26</version>
        </dependency>
    </dependencies>
</project>2.db.properties
driver.driver=com.mysql.cj.jdbc.Driver
driver.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver.username=root
driver.password=6666663.mybaits-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>
        <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。默认:true  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性 . 默认:true-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--        开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <!-- 数据库连接相关配置 ,db.properties文件中的内容-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver.driver}"/>
                <property name="url" value="${driver.url}"/>
                <property name="username" value="${driver.username}"/>
                <property name="password" value="${driver.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="cn.hdc.mapper"/>
    </mappers>
</configuration>4.MybaitsUtils工具类
package cn.hdc.utils;
 
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.Reader;
 
public class MybaitsUtils {
    private static SqlSessionFactory sqlSessionFactory = null;
 
    static {
        try {
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 
    public static SqlSession getSession() {
        return sqlSessionFactory.openSession();
    }
}5.建库建表

 
 
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);
6.根据表字段创建实体Product
package cn.hdc.pojo;
public class Product {
    private int id;
    private String goodsname;
    private double price;
    private int typeid;
    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", goodsname='" + goodsname + '\'' +
                ", price=" + price +
                ", typeid=" + typeid +
                '}';
    }
    public int getId() {
        return id;
    }
    public void setId(int 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 int getTypeid() {
        return typeid;
    }
    public void setTypeid(int typeid) {
        this.typeid = typeid;
    }
}
7.根据表字段创建实体Category
package cn.hdc.pojo;
import java.util.List;
public class Category {
    private int id;
    private String typename;
    private List<Product> productList;
    @Override
    public String toString() {
        return "Category{" +
                "id=" + id +
                ", typename='" + typename + '\'' +
                ", productList=" + productList +
                '}';
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTypename() {
        return typename;
    }
    public void setTypename(String typename) {
        this.typename = typename;
    }
    public List<Product> getProductList() {
        return productList;
    }
    public void setProductList(List<Product> productList) {
        this.productList = productList;
    }
}
8.创建CategoryMapper接口,声明要实现的方法
package cn.hdc.mapper;
import cn.hdc.pojo.Category;
public interface CategoryMapper {
    public Category findCategoryWithProduct(Integer id);
}
9.创建CategoryMapper.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="cn.hdc.mapper.CategoryMapper">
    <select id="findCategoryWithProduct" resultMap="categoryWithProduct">
        select p.id pid,
               p.goodsname,
               p.price,
               c.id cid,
               c.typename
        from category c,
             product p
        where c.id = p.category_id
          and c.id = #{id}
    </select>
    <resultMap id="categoryWithProduct" type="cn.hdc.pojo.Category">
        <id property="id" column="cid"></id>
        <result property="typename" column="typename"></result>
        <collection property="productList"
                    javaType="list"
                    ofType="cn.hdc.pojo.Product">
            <id property="id" column="pid"></id>
            <result property="goodsname" column="goodsname"></result>
            <result property="price" column="price"></result>
        </collection>
    </resultMap>
</mapper>9.创建测试类
package cn.hdc.mapper;
import cn.hdc.pojo.Category;
import cn.hdc.utils.MybaitsUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class CategoryMapperTest {
    private SqlSession session;
    @Before
    public void setUp() throws Exception {
        this.session = MybaitsUtils.getSession();
    }
    @After
    public void tearDown() throws Exception {
        this.session.commit();
        this.session.close();
    }
    @Test
    public void findCategoryWithProduct() {
        Category category = session.selectOne("cn.hdc.mapper.CategoryMapper.findCategoryWithProduct", 2);
        System.out.println(category);
    }
}10.运行结果:

11.项目结构:
 
 



















