MyBatis框架学习笔记(四):动态SQL语句、映射关系和缓存

news2024/9/22 19:42:35

动态 SQL 语句-更复杂的查询业务需求

1.1 动态 SQL-官方文档

(1)文档地址: mybatis – MyBatis 3 | 动态 SQL

(2)为什么需要动态 SQL

  • 动态 SQL MyBatis 的强大特性之一
  • 使用 JDBC 或其它类似的框架,根据不同条件拼接 SQL 语句非常麻烦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号等
  • SQL 映射语句中的强大的动态 SQL 语言, 可以很好的解决这个问题

1.2 动态 SQL-基本介绍 

基本介绍
  • 在一个实际的项目中,sql 语句往往是比较复杂的
  • 为了满足更加复杂的业务需求,MyBatis 的设计者,提供了动态生成 SQL 的功能。

动态 SQL 必要性

  • 比如我们查询Monster 时,如果程序员输入的age 不大于0, 我们的sql语句就不带age 。
  • 更新 Monster 对象时,没有设置的新的属性值,就保持原来的值,设置了新的值,才更新

解决方案分析

  • 从上面的需求我们可以看出,有时我们在生成 sql 语句时,在同一个方法中,还要根据 不同的情况生成不同的 sql 语句
  • 解决方案: MyBatis 提供的动态 SQL 机制

动态 SQL 常用标签

动态 SQL 提供了如下几种常用的标签,类似我们 Java 的控制语句:

(1)if [判断]

(2)where [拼接 where 子句]

(3)choose/when/otherwise [类似 java 的 switch 语句, 注意是单分支]

(4)foreach [类似 in ]

(5)trim [替换关键字/定制元素的功能]

(6)set [在 update 的 set 中,可以保证进入 set 标签的属性被修改,而没有进入 set 的,保持原来的值]

1.3 动态 SQL-案例演示

1.3.1 新建 Module dynamic-sql 

(1)在原来的项目中,新建 dynamic-sql 模块演示动态 SQL 的使用

(2)新建 Module 后,先创建需要的包,再将需要的文件/资源拷贝过来(这里我们拷贝 Monster.java、resources/jdbc.properties 和 mybatis-config.xml)

(3)创建 MonsterMapper.java MonsterMapper.xml 和 MonsterMapperTest.java

1.3.2 if 标签应用实例 

需求:请查询 age 大于 10 的所有妖怪,如果程序员输入的 age 不大于 0, 则输出所有 的妖怪 

代码实现

(1)修改 MonsterMapper.java ,增加接口方法

//根据age查询结果
public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);

 (2)修改 MonsterMapper.xml,sql实现

<!--
1. 配置方法public List<Monster> findMonsterByAge(@Param(value="age") Integer age);
2. 请查询age 大于 10 的所有妖怪,如果程序员输入的age 不大于 0, 则输出所有的妖怪
3. 如果使用原来的#{age} 在test表达式是取不出入参值
4. 解决方案是使用@Param
-->
<select id="findMonsterByAge" parameterType="int" resultType="monster">
    select * from `monster` where 1 = 1
    <if test="age >= 0">
        and `age` > ${age}
    </if>
</select>

(3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByAge() {
    List<Monster> monsters =
            monsterMapper.findMonsterByAge(-1);
    for (Monster monster : monsters) {
        System.out.println("monster--" + monster);
    }
    if (sqlSession != null) {
        sqlSession.close();
    }
    System.out.println("操作成功~");
}

 测试效果

1.3.3 where 标签应用实例 

需求:查询 id 大于 20 的,并且名字包含 "狐狸精" 的所有妖怪, 如果名字为空 , 就不带名字条件, 如果输入的 id 小于 0, 就不带 id 的条件

(1)修改 MonsterMapper.java ,增加接口方法

//根据id和名字来查询结果
public List<Monster> findMonsterByIdAndName(Monster monster);

 (2)修改 MonsterMapper.xml,sql实现

<!--
    1、配置public List<Monster> findMonsterByIdAndName(Monster monster);
    2. 如果我们入参是对象,test表达式中, 直接使用对象的属性名即可
    3. where标签,会在组织动态sql时,加上where
    4. mybatis底层自动的去掉多余的AND
-->
<select id="findMonsterByIdAndName" parameterType="monster" resultType="monster">
    select * from `monster`
    <where>
        <if test="age >= 0">
            `age` > #{age}
        </if>
        <if test="name != null and name != ''">
            and `name` like '%${name}%'
        </if>
    </where>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByIdAndName(){
    Monster monster = new Monster();
    monster.setAge(20);
    monster.setName("狐狸精");
    List<Monster> monsters =
            monsterMapper.findMonsterByIdAndName(monster);

    for (Monster o : monsters) {
        System.out.println("monster--" + o);
    }

    if (sqlSession != null){
        sqlSession.close();
    }
    System.out.println("操作成功~");
}

  测试效果

1.3.4 choose/when/otherwise 应用实例

需求:如果给的 name 不为空,就按名字查询妖怪,name 为空并且 id>0,就按 id 来查询妖怪, 如果name 为空 并且 id<0,则以 id > 10 为查询条件。要求使用 choose/when/otherwise 标签实现, 传入参数要求使用 Map 

(1)修改 MonsterMapper.java ,增加接口方法

//测试choose标签的使用
public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map);

(2)修改 MonsterMapper.xml,sql实现

<!--
    1、配置/实现public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map)
    2、  1) 如果给的name不为空,就按名字查询妖怪,
        2) 如果指定的id>0,就按id来查询妖怪
        3) 如果前面两个条件都不满足, 就默认查询 `age` > 10的
        4) 使用mybatis 提供choose-when-otherwise
    -->
<select id="findMonsterByIdOrName_choose" parameterType="map" resultType="monster">
    select * from `monster`
    <where>
        <choose>
            <when test="name != null and name != ''">
                `name` like '%${name}%'
            </when>
            <when test="age >= 0">
                `age` > #{age}
            </when>
            <otherwise>
                `age` > 10
            </otherwise>
        </choose>
    </where>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByIdOrName_choose(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("age",-1);
    map.put("name","");
    List<Monster> monsters =
            monsterMapper.findMonsterByIdOrName_choose(map);

    for (Monster o : monsters) {
        System.out.println("monster--" + o);
    }

    if (sqlSession != null){
        sqlSession.close();
    }
    System.out.println("操作成功~");
}

 测试效果

1.3.5 forEach 标签应用实例 

forEach 标签说明:可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

需求:查询 monster_id 为 20, 22, 34 的妖怪 

(1)修改 MonsterMapper.java ,增加接口方法

//测试foreach的标签使用
public List<Monster> findMonsterById_forEach(Map<String, Object> map)

 (2)修改 MonsterMapper.xml,sql实现

<!--
        1、配置/实现public List<Monster> findMonsterById_forEach(Map<String, Object> map);
        2、查询id 为 5, 6, 7 的妖怪
        3、是foreach标签
        4. 入参map 中 会如何传入id值 k-v ids - [集合,比如List 10,12,14], 即
           map 入参中应当有 ids-[10,12,14]
    -->
<select id="findMonsterById_forEach" parameterType="map" resultType="monster">
    select * from `monster`
    <!-- 解读
        3. 如果ids不为空,则使用foreach标签进行遍历
        4. collection="ids" 对应入参map的 key 为 ids
        5. item="id" 在遍历ids集合时,每次取出的值,对应的变量id
        6. open="where id in (" 表示开头的字符串
        7. separator="," 遍历出来的多个值之间的 分隔符号
        8. close=")" 表示结尾的字符串
        9. #{id} 对应的就是 item="id",即遍历出来的值
    -->
        <if test="ids != null and ids != ''">
            <foreach collection="ids" index="" item="id" open="where id in ("
                     separator="," close=")">
                #{id}
            </foreach>
        </if>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterById_forEach() {

    Map<String, Object> map = new HashMap<>();
    map.put("ids", Arrays.asList(5, 6, 7));

    List<Monster> monsters =
            monsterMapper.findMonsterById_forEach(map);

    for (Monster monster : monsters) {
        System.out.println("monster--" + monster);
    }


    if (sqlSession != null) {
        sqlSession.close();
    }
    System.out.println("操作成功~");

}

 测试效果

1.3.6 trim 标签应用实例

trim 可以替换一些关键字

要求:按名字查询妖怪,在动态添加where,并且如果where 子句以开头 and | or 就去除。即使用trim 标签 实现 等价于 where标签的效果

(1)修改 MonsterMapper.java ,增加接口方法

//trim标签的使用
public List<Monster> findMonsterByName_Trim(Map<String, Object> map);

 (2)修改 MonsterMapper.xml,sql实现

suffixOverrides 表示行末尾,prefixOverrides 表示行开头

<!--
1. 配置/实现 public List<Monster> findMonsterByName_Trim(Map<String, Object> map);
2. 按名字和年龄 查询妖怪,如果sql语句开头有 and | or  就替换成 where
3. 如果要实现这个功能,其实使用where标签是一样的效果 [加入where 同时会去掉多余的and]
4. trim prefix="WHERE" prefixOverrides="and|or|wwj" 若子句的开头为 “AND” 或 “OR  或"wwj"
, 就去除
-->
<select id="findMonsterByName_Trim" parameterType="map" resultType="Monster">
    SELECT * FROM `monster`
    <trim prefix="WHERE" prefixOverrides="and|or|wwj">
        <if test="name != null and name != ''">
            AND `name` = #{name}
        </if>
        <if test="age != null and age != ''">
            AND `age` > #{age}
        </if>
    </trim>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByName_Trim() {
    Map<String, Object> map = new HashMap<>();
    map.put("name", "狐狸精-100");
    map.put("age", 10);

    List<Monster> monsters =
            monsterMapper.findMonsterByName_Trim(map);

    for (Monster monster : monsters) {
        System.out.println("monster--" + monster);
    }

    if (sqlSession != null) {
        sqlSession.close();
    }
    System.out.println("操作成功~");

}

测试效果

1.3.7 set 标签应用实例 

set标签说明:

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

需求: 请对指定 id 的妖怪进行 修改,如果没有设置新的属性,则保持原来的值 

(1)修改 MonsterMapper.java ,增加接口方法

//测试Set标签
public void updateMonster_set(Map<String, Object> map);

(2)修改 MonsterMapper.xml,sql实现

<!--
1. 配置/实现 public void updateMonster_set(Map<String, Object> map);
2. 请对指定id 的妖怪进行 修改,如果没有设置新的属性,则保持原来的值
3. 入参要根据sql语句来配合 map [age-10, email-'xx@sohu.com'...]
4. set标签会处理多余的 ,
-->
<update id="updateMonster_set" parameterType="map">
    UPDATE `monster`
    <set>
        <if test="age != null and age != ''">
            `age` = #{age} ,
        </if>
        <if test="email != null and email != ''">
            `email` = #{email} ,
        </if>
        <if test="name != null and name != ''">
            `name` = #{name} ,
        </if>
        <if test="birthday != null and birthday != ''">
            `birthday` = #{birthday} ,
        </if>
        <if test="salary != null and salary != ''">
            `salary` = #{salary} ,
        </if>
        <if test="gender != null and gender != ''">
            `gender` = #{gender} ,
        </if>
    </set>
    WHERE id = #{id}
</update>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void updateMonster_set() {
    Map<String, Object> map = new HashMap<>();
    map.put("id", 1);
    map.put("name", "牛魔王5-100");
    map.put("age", 76);
    map.put("email", "wwj@qq.com");

    monsterMapper.updateMonster_set(map);

    //修改需要有commit
    if (sqlSession != null) {
        sqlSession.commit();
        sqlSession.close();
    }
    System.out.println("操作成功~");
}

 测试效果

2 映射关系一对一

2.1 映射关系-官方文档

文档地址: mybatis – MyBatis 3 | XML 映射器

2.2 映射关系 1 1-基本介绍

基本介绍:项目中 1 对 1 的关系是一个基本的映射关系,比如:Person(人) --- IDCard(身份证) 

2.3 映射关系 1 1-映射方式 

2.3.1 映射方式

(1)通过配置 XxxMapper.xml 实现 1 对 1 [配置方式]

(2)通过注解的方式实现 1 对 1 [注解方式]

2.3.2 配置 Mapper.xml 的方式-应用实例

2.3.2.1 方式1

说明:通过配置 XxxMapper.xml 的方式来实现下面的 1 对 1 的映射关系,实现级联查询,通过 person 可以获取到对应的 idencard 信息 

完成功能示意(如下) 

person--Person{id=1, name='张三', card=IdenCard{id=1,card_sn='111111111111110'}}

 代码实现

(1)创建 person 表和 idencard

CREATE TABLE idencard(
id INT PRIMARY KEY AUTO_INCREMENT,
card_sn VARCHAR(32) NOT NULL DEFAULT '' 
);

CREATE TABLE person(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) NOT NULL DEFAULT '',
card_id INT , --对应idencard的主键id
FOREIGN KEY (card_id) REFERENCES idencard(id) --外键
);

INSERT INTO idencard VALUES(1,'111111111111110');
INSERT INTO person VALUES(1,'张三',1);
SELECT * FROM person;
SELECT * FROM idencard;

(2)创建新的 module(mybatis-mapping), 相关配置文件可以从上一个 module 拷贝 (这里我们拷贝resources/jdbc.properties 和 mybatis-config.xml)。嫌麻烦这一步可以省略,这里这是为了直观,所以一个章节使用一个module

(3)创 建 entity: src\main\java\com\entity\IdenCard.java 和 src\main\java\com\entity\Person.java

package com.entity;

public class IdenCard {

    private Integer id;
    private String card_sn;

    //通过查询IdenCard 可以级联查询得到person
    private Person person;

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCard_sn() {
        return card_sn;
    }

    public void setCard_sn(String card_sn) {
        this.card_sn = card_sn;
    }

    @Override
    public String toString() {
        return "IdenCard{" +
                "id=" + id +
                ", card_sn='" + card_sn + '\'' +
                ", person=" + person +
                '}';
    }
}
package com.entity;

public class Person {

    private Integer id;
    private String name;
    //因为我们的需要实现一个级联操作, 一个人需要对应一个身份证
    //这里需要直接定义IdenCard对象属性
    private IdenCard 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 IdenCard getCard() {
        return card;
    }

    public void setCard(IdenCard card) {
        this.card = card;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", card=" + card +
                '}';
    }
}

(4)创建 com\mapper\IdenCardMapper.java接口 和 com\mapper\IdenCardMapper.xml映射文件

package com.hspedu.mapper;

import com.hspedu.entity.IdenCard;

public interface IdenCardMapper {
    //根据id获取到身份证序列号
    public IdenCard getIdenCardById(Integer id);
}
<?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.mapper.IdenCardMapper">
    <select id="getIdenCardById" parameterType="integer" resultType="idenCard">
        select * from `idencard` where `id`=#{id}
    </select>
</mapper>

  (5)创建 PersonMapper.java PersonMapper.xml

package com.mapper;

import com.entity.Person;

public interface PersonMapper {

    //通过Person的id获取到Person,包括这个Person关联的IdenCard对象[级联查询]
    public Person getPersonById(Integer id);


}
<?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.mapper.PersonMapper">

    <!--
        1、配置/实现public Person getPersonById(Integer id);
        2、完成通过Person的id获取到Person,包括这个Person关联的IdenCard对象[级联查询]
        3. 自定义resultMap 搞定 映射返回的结果
    -->
    <resultMap id="PersonResultMap" type="Person">
        <!--id标签 – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
            1.property="id" 表示person 属性 id ,通常是主键
            2.column="id" 表示对应表的字段
        -->
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--association标签 – 一个复杂类型的关联
        1. property="card" 表示 Person对象的 card 属性
        2. javaType="IdenCard" 表示card 属性 的类型
        3. column="id" 是从我们的 下面这个语句查询后返回的字段
        SELECT *  FROM `person`,`idencard` WHERE `person`.id=1
        AND `person`.card_id = `idencard`.id
        -->
        <association property="card" javaType="IdenCard">
            <result property="id" column="id"/>
            <result property="card_sn" column="card_sn"/>
        </association>
    </resultMap>
    <select id="getPersonById" parameterType="Integer"
            resultMap="PersonResultMap">
        SELECT *  FROM `person`,`idencard` WHERE `person`.id = #{id}
                                             AND `person`.card_id = `idencard`.id
    </select>


</mapper>

 (6)创建 src\test\java\com\mapper\IdenCardMapperTest.java 和src\test\java\com\map per\PersonMapperTest.java , 进行测试 

package com.mapper;

import com.entity.IdenCard;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

public class IdenCardMapperTest {

    SqlSession sqlSession;
    IdenCardMapper idenCardMapper;

    @Before
    public void init(){
        sqlSession = MyBatisUtils.getSqlSession();
        idenCardMapper = sqlSession.getMapper(IdenCardMapper.class);
    }

    @Test
    public void getIdenCardById(){
        int id = 1;
        IdenCard idenCard = idenCardMapper.getIdenCardById(id);
        System.out.println("idenCard=" + idenCard);
    }
}
package com.mapper;

import com.entity.Person;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

public class PersonMapperTest {
    SqlSession sqlSession;
    PersonMapper personMapper;

    @Before
    public void init(){
        sqlSession = MyBatisUtils.getSqlSession();
        personMapper = sqlSession.getMapper(PersonMapper.class);
    }

    @Test
    public void getPersonById(){
        Person person = personMapper.getPersonById(1);
        System.out.println("person--" + person);
        if (sqlSession != null){
            sqlSession.close();
        }
    }

}

测试效果

2.3.2.2 方式2

(1)修改PersonMapper.java 接口增加方法 PersonMapper.xml 配置相应sql映射

//通过Person的id获取到Person,包括这个Person关联的IdenCard对象,方式2
public Person getPersonById2(Integer id);
<!--
    1、方式2
    1) 先通过 SELECT * FROM `person` WHERE `id` = #{id} 返回 person信息
    2) 再通过 返回的card_id 值,再执行操作,得到IdenCard 数据
-->
<resultMap id="PersonResultMap2" type="Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!--
    1. mybatis第二种方式核心思想: 将多表联查,分解成单表操作 , 这样简洁,而且易于维护 ,推荐
    2. property="card": 表示 Person对象的 card 属性
    3. column="card_id" 这个是
    SELECT * FROM `person` WHERE `id` = #{id}  返回的 字段 card_id 信息/数据
    4. 返回的 字段 card_id 信息/数据 作为getIdenCardById入参, 来执行
    -->
    <association property="card" column="card_id"
                 select="com.hspedu.mapper.IdenCardMapper.getIdenCardById" />
</resultMap>
<select id="getPersonById2" parameterType="Integer" resultMap="PersonResultMap2">
    SELECT * FROM `person` WHERE `id` = #{id}
</select>

(2)修改 PersonMapperTest.java 进行测试

@Test
public void getPersonById2(){
    Person person = personMapper.getPersonById2(1);
    System.out.println("person--" + person);
    if (sqlSession != null){
        sqlSession.close();
    }
}

测试效果

2.3.3 注解的方式实现-应用实例

说明:通过注解的方式来实现下面的 1 对 1 的映射关系,实现级联查询,通过 person 可以获取到 对应的 identcard 信息 (在实际开发中还是推荐使用配置方式)

(1)创建 com\mapper\IdenCardMapperAnnotaion.java

package com.mapper;

import com.entity.IdenCard;
import org.apache.ibatis.annotations.Select;

/**
 * IdenCardMapperAnnotation: 使用注解方式实现1对1的映射
 */
public interface IdenCardMapperAnnotation {
    //根据id获取到身份证
    //这个方法不需要返回任何级联对象
    @Select("SELECT * FROM `idencard` WHERE `id` = #{id}")
    public IdenCard getIdenCardById(Integer id);
}

 (2)创建 com\mapper\PersonMapperAnnotation.java

package com.mapper;

import com.entity.Person;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

public interface PersonMapperAnnotation {

    //这里注解实现方法
    //注解的形式就是对前面xml配置方式的体现
    @Select("SELECT * FROM `person` WHERE `id` = #{id}")
    @Results({
          @Result(id = true, property = "id", column = "id"),
          @Result(property = "name", column = "name"),
          @Result(property = "card", column = "card_id",
                  one = @One(select = "com.mapper.IdenCardMapper.getIdenCardById"))
    })
    public Person getPersonById(Integer id);
}

 (3)创建 com\mapper\PersonMapperAnnotationTest.java 完成测试

package com.mapper;

import com.entity.Person;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

public class PersonMapperAnnotationTest {

    //属性
    private SqlSession sqlSession;
    private PersonMapperAnnotation personMapperAnnotation;

    //初始化
    @Before
    public void init() {
        //获取到sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        personMapperAnnotation = sqlSession.getMapper(PersonMapperAnnotation.class);
    }

    @Test
    public void getPersonById() {

        Person person = personMapperAnnotation.getPersonById(1);
        System.out.println("person----" + person);
        if(sqlSession != null) {
            sqlSession.close();
        }

    }
}

测试效果

3 映射关系多对一 

.1 映射关系-官方文档

 文档地址: mybatis – MyBatis 3 | XML 映射器 

3.2 映射关系多对 1-基本介绍 

基本介绍

(1)项目中多对 1 的关系是一个基本的映射关系, 多对 1, 也可以理解成是 1 对多

  • User --- Pet: 一个用户可以养多只宠物
  • Dep ---Emp : 一个部门可以有多个员工

特别说明

(1)在实际的项目开发中, 要求会使用双向的多对一的映射关系

(2)说明:什么是双向的多对一的关系 : 比如通过 User 可以查询到对应的 Pet, 反过来,通过 Pet 也可以级联查询到对应的 User 信息

(3)多对多的关系,是在多对 1 的基础上扩展即可. 

3.3 映射关系多对 1-映射方式 

3.3.1 映射方式

(1)方式 1:通过配置 XxxMapper.xml 实现多对 1

(2)方式 2:通过注解的方式实现 多对 1 

3.3.2 配置 Mapper.xml 方式-应用实例

需求说明: 实现级联查询,通过 user 的 id 可以查询到用户信息,并可以查询到关联的 pet 信息,反过来,通过 Pet 的 id 可以查询到 Pet 的信息,并且可以级联查询到它的主人 User 对象信息

(1)创建 mybatis_user 和 mybatis_pet 表

CREATE TABLE mybatis_user(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) NOT NULL DEFAULT '' 
);
CREATE TABLE mybatis_pet(
id INT PRIMARY KEY AUTO_INCREMENT,
nickname VARCHAR(32) NOT NULL DEFAULT '',
user_id INT ,
FOREIGN KEY (user_id) REFERENCES mybatis_user(id)
);
INSERT INTO mybatis_user
VALUES(NULL,'宋江'),(NULL,'张飞');
INSERT INTO mybatis_pet
VALUES(1,'黑背',1),(2,'小哈',1);
INSERT INTO mybatis_pet
VALUES(3,'波斯猫',2),(4,'贵妃猫',2);
SELECT * FROM mybatis_user;
SELECT * FROM mybatis_pet;

(2)创 建 entity : entity\Pet.java 和 entity\User.java 

package com.entity;

import java.util.List;

public class User {

    private Integer id;
    private String name;
    //因为一个user可以养多个宠物,mybatis 使用集合List<Pet>体现这个关系
    private List<Pet> pets;

    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 List<Pet> getPets() {
        return pets;
    }

    public void setPets(List<Pet> pets) {
        this.pets = pets;
    }

    //这toString会带来麻烦?=>会造成StackOverFlow
    //@Override
    //public String toString() {
    //    return "User{" +
    //            "id=" + id +
    //            ", name='" + name + '\'' +
    //            ", pets=" + pets +
    //            '}';
    //}
}
package com.entity;

public class Pet {

    private Integer id;
    private String nickname;
    //一个pet对应一个主人 User对象
    private User user;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

}

(3)创建 PetMapper.java 和 UserMapper.java 

package com.mapper;

import com.entity.Pet;

import java.util.List;

public interface PetMapper {
    //通过User的id来获取pet对象,可能有多个,因此使用List接收
    public List<Pet> getPetByUserId(Integer userId);

    //通过pet的id获取Pet对象, 同时会查询到pet对象关联的user对象
    public Pet getPetById(Integer id);

}
package com.mapper;

import com.entity.User;

public interface UserMapper {
    //通过id获取User对象
    public User getUserById(Integer id);
}

(4)创建 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="com.mapper.UserMapper">

    <!--
   1、配置/实现 public User getUserById(Integer id);
   2、思路(1) 先通过user-id 查询得到user信息 (2) 再根据user-id查询对应的pet信息
     并映射到User-List<Pet> pets
   -->
    <resultMap id="userMap" type="com.entity.User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--因为pets属性是集合,因此这里需要是collection标签来处理
        1. ofType="Pet" 指定返回的集合中存放的数据类型Pet
        2. collection 表示 pets 是一个集合
        3. property="pets" 是返回的user对象的属性 pets
        4. column="id" SELECT * FROM `mybatis_user` WHERE `id` = #{id} 返回的id字段对应的值
        -->
        <collection property="pets" column="id" ofType="pet"
                    select="com.mapper.PetMapper.getPetByUserId"/>
    </resultMap>
    <select id="getUserById" parameterType="integer" resultMap="userMap">
        select * from `mybatis_user` where id=#{id}
    </select>

</mapper>

(5) 创建 PetMapper.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.mapper.PetMapper">

    <!--
        1、通过User的id来获取pet对象,可能有多个,因此使用List接收
        2、public List<Pet> getPetByUserId(Integer userId);
    -->
    <resultMap id="PetResultMap" type="Pet">
        <id property="id" column="id"/>
        <result property="nickname" column="nickname"/>
        <association property="user" column="user_id"
                     select="com.mapper.UserMapper.getUserById" />
    </resultMap>
    <select id="getPetByUserId" parameterType="integer" resultMap="PetResultMap">
        select * from `mybatis_pet` where user_id=#{userId}
    </select>

    <!--
        1. 这里直接复用PetResultMap
        2. 实现/配置public Pet getPetById(Integer id);
        3. 通过pet的id获取Pet对象
    -->
    <select id="getPetById"
            parameterType="Integer"
            resultMap="PetResultMap">
        SELECT * FROM `mybatis_pet` WHERE `id` = #{id}
    </select>
</mapper>

 (6)创建 PetMapperTest.java 完成测试

package com.mapper;

import com.entity.Pet;
import com.entity.User;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

public class PetMapperTest {
    //属性
    private SqlSession sqlSession;
    private PetMapper petMapper;

    //初始化
    @Before
    public void init() {
        //获取到sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        petMapper = sqlSession.getMapper(PetMapper.class);
    }

    @Test
    public void getPetByUserId() {
      List<Pet> pets = petMapper.getPetByUserId(2);
      for (Pet pet : pets) {
            System.out.println("pet信息-" + pet.getId() + "-" + pet.getNickname());
            User user = pet.getUser();
            System.out.println("user信息 name-" + user.getName());
        }
        if(sqlSession != null) {
            sqlSession.close();
        }
    }
    @Test
    public void getPetById() {
        Pet pet = petMapper.getPetById(2);
        System.out.println("pet信息-" + pet.getId() + "-" + pet.getNickname());
        User user = pet.getUser();
        System.out.println("user信息-" + user.getId() + "-" + user.getName());
        if(sqlSession != null) {
            sqlSession.close();
        }
    }

}

 测试效果

3.3.3 注解实现多对 1 映射-应用实例

需求说明: 通过注解的方式来实现下面的多对 1 的映射关系,实现级联查询,完成前面完成 的任务,通过 User-->Pet 也可 Pet->User , 在实际开发中推荐使用配置方式来做

(1)创建 UserMapperAnnotation.java

package com.mapper;

import com.entity.User;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

/**
 * UserMapperAnnotation:以注解的方式来配置多对一
 */
public interface UserMapperAnnotation {

    //通过id获取User对象
    @Select("SELECT * FROM `mybatis_user` WHERE `id` = #{id}")
    @Results({
          @Result(id = true, property = "id", column = "id"),
          @Result(property = "name", column = "name"),
          //这里请小伙伴注意,pets属性对应的是集合
          @Result(property = "pets",
                  column = "id",
                  many = @Many(select = "com.mapper.PetMapperAnnotation.getPetByUserId"))
    })
    public User getUserById(Integer id);
}

 (2)创建 PetMapperAnnotation.java

package com.mapper;

import com.entity.Pet;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface PetMapperAnnotation {

    //通过User的id来获取pet对象,可能有多个,因此使用List接收
    
    //id = "PetResultMap" 就是给我们的Results[Result Map] 指定一个名字
    //,目的是为了后面复用
    @Select("SELECT * FROM `mybatis_pet` WHERE `user_id` = #{userId}")
    @Results(id = "PetResultMap", value = {
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "nickname", column = "nickname"),
            @Result(property = "user",
                    column = "user_id",
                    one = @One(select = "com.mapper.UserMapperAnnotation.getUserById"))

    })
    public List<Pet> getPetByUserId(Integer userId);


    //通过pet的id获取Pet对象, 同时会查询到pet对象关联的user对象

    /**
     * @ResultMap("PetResultMap") 使用/引用我们上面定义的 Results[ResultMap]
     */
    @Select("SELECT * FROM `mybatis_pet` WHERE `id` = #{id}")
    @ResultMap("PetResultMap")
    public Pet getPetById(Integer id);
}

(3)创建 UserMapperAnnotationTest.java 完成测试

package com.mapper;

import com.entity.Pet;
import com.entity.User;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

public class UserMapperAnnotationTest {
    //属性
    private SqlSession sqlSession;
    private UserMapperAnnotation userMapperAnnotation;

    //初始化
    @Before
    public void init() {
        //获取到sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        userMapperAnnotation = sqlSession.getMapper(UserMapperAnnotation.class);
    }

    @Test
    public void getUserById() {

        User user = userMapperAnnotation.getUserById(2);
        System.out.println("user信息-" + user.getId() + "-" + user.getName());

        List<Pet> pets = user.getPets();
        for (Pet pet : pets) {
            System.out.println("宠物信息-" + pet.getId() + "-" + pet.getNickname());
        }

        if(sqlSession != null) {
            sqlSession.close();
        }
    }
}

 测试效果

4 缓存-提高检索效率的利器

4.1 缓存-官方文档

文档地址:mybatis – MyBatis 3 | XML 映射器 

4.2 一级缓存

4.2.1 基本介绍

(1)默认情况下,mybatis 是启用一级缓存的/本地缓存/local Cache,它是 SqlSession 级别的

(2)同一个 SqlSession 接口对象调用了相同的 select 语句,会直接从缓存里面获取,而不是再去查询数据库 

(3)一级缓存原理图

4.2.2 一级缓存快速入门 

需求: 当我们第 1 次查询 id=1 的 Monster 后,再次查询 id=1 的 monster 对象,就会直接 从一级缓存获取,不会再次发出 sql

(1)创建新module: mybatis_cache , 必要的文件和配置直接从mybatis_quickstart module 拷贝即可,需要拷贝的文件和配置如图

(2)使用 MonsterMapperTest.java , 运行 getMonsterById() 通过查看日志输出, 结论我们多次运行,总是会发出 SQL

(3)修改MonsterMapperTest.java, 增加测试方法, 测试一级缓存的基本使用(无需配置,因为默认的就是一级缓存)

//测试一级缓存
@Test
public void level1CacheTest() {

    //查询id=1的monster
    Monster monster = monsterMapper.getMonsterById(1);
    System.out.println("monster=" + monster);

    //再次查询id=1的monster
    //当我们再次查询 id=1的Monster时,直接从一级缓存获取,不会再次发出sql
    System.out.println("--一级缓存默认是打开的,当你再次查询相同的id时, 不会再发出sql----");
    Monster monster2 = monsterMapper.getMonsterById(1);
    System.out.println("monster2=" + monster2);


    if (sqlSession != null) {
        sqlSession.close();
    }
}

4.2.3 一级缓存失效分析 

(1)关闭 sqlSession 会话后, 再次查询,会到数据库查询

(2)当执行 sqlSession.clearCache() 会使一级缓存失效

(3)当对同一个 monster 修改(update),该对象在一级缓存会失效

4.3 二级缓存

4.3.1 基本介绍

(1)二级缓存和一级缓存都是为了提高检索效率的技术

(2)最大的区别就是作用域的范围不一样,一级缓存的作用域是 sqlSession 会话级别,在一次 会话有效,而二级缓存作用域是全局范围,针对不同的会话都有效

(3)二级缓存原理图 

4.3.2 二级缓存快速入门

(1)mybatis-config.xml 配置中开启二级缓存 

<configuration>

    <properties resource="jdbc.properties"/>
    
    <settings>
        <!--配置MyBatis自带的日志输出-查看原生的sql-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--
        1、全局性地开启或关闭所有映射器配置文件中已配置的任何缓存, 可以理解这是一个总开关
        2、默认就是: true
        -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
。。。。。
</configuration>
(2)使用二级缓存时 entity 类实现序列化接口 (serializable),因为二级缓存可能使用到序 列化技术(大部分情况用不到,一些第三方缓存库可能用得到,所以可以不配置,等需要的时候再配置也可以)
public class Monster implements Serializable {

(3)在对应的 XxxMapper.xml 中设置二级缓存的策略 

<mapper namespace="com.mapper.MonsterMapper">

    <!--
        1、配置二级缓存: 是mybatis自带
        2、eviction="FIFO" – 先进先出:按对象进入缓存的顺序来移除它们。
        默认LRU,最近最少使用。可选属性还有"SOFT"软引用,"WEAK"弱引用
        3. flushInterval 刷新间隔 是毫秒单位 60000 表示 60s
        4. size="512": 引用数目, 属性可以被设置为任意正整数, 默认1024
        5. readOnly="true": (只读)属性可以被设置为 true 或 false: 如果我们只是用于读操作,
        建议设置成 true, 这样可以提示效率, 如果有修改操作,设置成 false, 默认就是false
    -->
    <cache eviction="FIFO" flushInterval="60000"
          size="512" readOnly="true"/>


    。。。。。。。
</mapper>

(4)修改 MonsterMapperTest.java , 完成测试

//测试二级缓存的使用
@Test
public void level2CacheTest() {

    //查询id=1的monster
    Monster monster = monsterMapper.getMonsterById(1);
    System.out.println("monster=" + monster);


    //这里关闭sqlSession
    if (sqlSession != null) {
        sqlSession.close();
    }

    //重新获取sqlSession
    sqlSession = MyBatisUtils.getSqlSession();
    //重新获取了monsterMapper
    monsterMapper = sqlSession.getMapper(MonsterMapper.class);
    //再次查询id=3的monster
    System.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存, " +
            "当你再次查询相同的id时, 依然不会再发出sql, 而是从二级缓存获取数据----");
    Monster monster2 = monsterMapper.getMonsterById(1);
    System.out.println("monster2=" + monster2);


    if (sqlSession != null) {
        sqlSession.close();
    }
}

测试效果 

4.3.3 注意事项和使用陷阱

(1)理解二级缓存策略的参数

<cache eviction="FIFO" flushInterval="30000" size="360" readOnly="true"/

上面的配置意思如下:

创建了 FIFO 的策略,每隔 30 秒刷新一次,最多存放 360 个对象而且返回的对象被认为是 只读的。

  • eviction:缓存的回收策略
  • flushInterval:时间间隔,单位是毫秒
  • size:引用数目,内存大就多配置点,要记住你缓存的对象数目和你运行环境的可用内存 资源数目。默认值是 1024
  • readOnly:true,只读

(2)四大策略

  • LRU – 最近最少使用的:移除最长时间不被使用的对象,它是默认
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

(3)如何禁用二级缓存

1) 在 mybatis-config.xml

<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存, 默认就是 true-->
<setting name="cacheEnabled" value="false"/>
</settings>

2)在 mapper\MonsterMapper.xml 中把下面这一行注释掉

<!--<cache eviction="FIFO" flushInterval="30000" size="360" readOnly="true"/>-->

3) 或者更加细粒度的, 在配置方法上指定

设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出 sql 去查询, 默认情况是 true,即该 sql 使用二级缓存。 注意:一般我们不需要去修改,使用默认的即可

(4)mybatis 刷新二级缓存的设置

<update id="updateMonster" parameterType="Monster" flushCache="true">
    UPDATE mybatis_monster SET NAME=#{name},age=#{age} WHERE id=#{id}
</update>

insert、update、delete 操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读 默认为 true,默认情况下为 true 即刷新缓存,一般不用修改。

(5)不会出现一级缓存和二级缓存中有同一个数据。因为二级缓存(数据)是在一级缓存关闭 之后才有的 

4.4 Mybatis 的一级缓存和二级缓存执行顺序

缓存执行顺序是:二级缓存-->一级缓存-->数据库 

说明:先看二级缓存有没有数据,如果没有再看一级缓存,一级缓存也没有数据,才会连接数据库。当我们关闭一级缓存的时候,如果配置了二级缓存,那么一级缓存的数据,会放入到二级缓存 

小实验,测试缓存执行顺序

(1)修改 MonsterMapperTest.java,增加如下方法

//演示二级缓存->一级缓存->DB执行的顺序
@Test
public void cacheSeqTest() {

    System.out.println("查询第1次");
    //DB, 会发出SQL, 分析cache hit ratio 0.0
    Monster monster1 = monsterMapper.getMonsterById(1);
    System.out.println(monster1);

    //这里关闭sqlSession, 一级缓存数据没有
    //当我们关闭一级缓存的时候,如果你配置二级缓存,那么一级缓存的数据,会放入到二级缓存
    sqlSession.close();

    sqlSession = MyBatisUtils.getSqlSession();
    monsterMapper = sqlSession.getMapper(MonsterMapper.class);

    System.out.println("查询第2次");
    //从二级缓存获取id=1 monster , 就不会发出SQL, 分析cache hit ratio 0.5
    Monster monster2 = monsterMapper.getMonsterById(1);
    System.out.println(monster2);

    System.out.println("查询第3次");
    //从二级缓存获取id=1 monster, 不会发出SQL, 分析cache hit ratio 0.6666
    Monster monster3 = monsterMapper.getMonsterById(1);
    System.out.println(monster3);

    if (sqlSession != null) {
        sqlSession.close();
    }
    System.out.println("操作成功");

}

 (2)运行结果:

4.5 EhCache 缓存

4.5.1 基本介绍

(1)配置文档(官方解释): https://www.cnblogs.com/zqyanywn/p/10861103.html

(2)文档说明(通俗解释): https://www.taobye.com/f/view-11-23.html 

(3)EhCache 是一个纯 Java 的缓存框架,具有快速、精干等特点

(4)MyBatis 有自己默认的二级缓存(前面我们已经使用过了),但是在实际项目中,往往使用的是更加专业的第三方缓存产品 作为 MyBatis 的二级缓存,EhCache 就是非常优秀的缓存产品

4.5.2 配置和使用 EhCache

(1)加入相关依赖, 修改 mybatis_cache\pom.xml

<dependencies>
    <!--引入ehcache核心库/jar-->
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.6.11</version>
    </dependency>
    <!--引入需要使用的slf4j-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
    <!--引入mybatis整合ehcache库/jar-->
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.2.1</version>
    </dependency>
</dependencies>

 (2)mybatis-config.xml 仍然打开二级缓存 

<settings>
        <!--
        1、全局性地开启或关闭所有映射器配置文件中已配置的任何缓存, 可以理解这是一个总开关
        2、默认就是: true
        -->
        <setting name="cacheEnabled" value="true"/>
</settings>

 (3)加入 mybatis_cache\src\main\resources\ehcache.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统宕机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      可理解为: TTI用于设置对象在cache中的最大闲置时间,就是 在一直不访问这个对象的前提下,这个对象可以在cache中的存活时间。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      可理解为: TTL用于设置对象在cache中的最大存活时间,就是 无论对象访问或是不访问(闲置),这个对象在cache中的存活时间
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>

    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>

</ehcache>

(4)在 XxxMapper.xml 中启用 EhCache , 记得把原来 MyBatis 自带的缓存配置注销

<!--配置/启用ehcache-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

(5)修改 MonsterMapperTest.java , 增加测试方法, 完成测试

//测试ehCache级缓存
@Test
public void ehCacheTest() {

    //查询id=1的monster
    Monster monster = monsterMapper.getMonsterById(1);
    //会发出SQL, 到db查询
    System.out.println("monster=" + monster);

    //这里关闭sqlSession, 一级缓存[数据]失效.=> 将数据放入到二级缓存 (ehcache)
    if (sqlSession != null) {
        sqlSession.close();
    }

    //重新获取sqlSession
    sqlSession = MyBatisUtils.getSqlSession();
    //重新获取了monsterMapper
    monsterMapper = sqlSession.getMapper(MonsterMapper.class);
    //再次查询id=1的monster
    System.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存(ehcache), " +
            "当你再次查询相同的id时, 不会再发出sql, 而是从二级缓存(ehcache)获取数据----");
    Monster monster2 = monsterMapper.getMonsterById(1);
    System.out.println("monster2=" + monster2);

    //再次查询id=3的monster, 仍然到二级缓存(ehcache), 获取数据, 不会发出sql
    Monster monster3 = monsterMapper.getMonsterById(1);
    System.out.println("monster3=" + monster3);

    if (sqlSession != null) {
        sqlSession.close();
    }

}

测试效果

4.5.3 EhCache 缓存-细节说明

 如何理解 EhCache 和 MyBatis 缓存的关系

(1)MyBatis 提供了一个接口 Cache【如右图,找到 org.apache.ibatis.cache.Cache ,关联源 码包就可以看到 Cache 接口】

(2)只要实现了该 Cache 接口,就可以作为二级缓存产品和 MyBatis 整合使用,Ehcache 就是实现了该接口 

(3)MyBatis 默认情况(即一级缓存)是使用的 PerpetualCache 类实现 Cache 接口的

(4)当我们使用了 Ehcahce 后,就是 EhcacheCache 类实现 Cache 接口的.

(5)我们看一下源码,发现缓存的本质就是 Map<Object,Object> 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于jeecgboot-vue3的Flowable流程同时支持bpmn流程设计器与仿钉钉流程设计器(全网首创)

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、新建流程的时候可以选择使用不同的流程设计器 2、选择bpmn流程设计器 3、选择仿钉钉流程设计器

百度“文心•跨模态大模型”又有新动态,支持内容分析时输出自定义标签库

大模型真正的价值在于应用。 一、基本概念 AI大模型具有强大的表征学习能力&#xff0c;能够在海量数据中提取有用的特征&#xff0c;为各种复杂任务提供解决方案。例如GPT-4o、BERT等模型的出现&#xff0c;不仅展示了大规模参数和复杂计算结构的优势&#xff0c;还在自然语…

django学习入门系列之第四点《案例 后台管理样例》

文章目录 往期回顾 前期准备&#xff1a; 导航新建&#xff0c;按钮表格 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><!-- 开发版本 --><link rel"stylesheet…

【操作系统】文件管理——文件的物理结构(个人笔记)

学习日期&#xff1a;2024.7.15 内容摘要&#xff1a;文件的物理结构&#xff0c;逻辑结构与物理结构 目录 引言 文件分配方式 连续分配 链接分配 隐式链接 显式链接 索引分配 索引块大小不够装入整个索引表怎么办&#xff1f; ①链接方案 ②多层索引 ③混合索引 …

5、 测试

这里写目录标题 1、自动化测试简介&#xff08;1&#xff09;自动化测试是什么&#xff08;2&#xff09;为什么要写测试测试节约你的时间发现错误&#xff0c;预防错误测试使得代码更有吸引力 2、基础测试策略3、开始写第一个测试&#xff08;1&#xff09;首先得有个bug&…

目标检测之单类别NMS

long time no see! 在目标检测中&#xff0c;常见的是多类别NMS&#xff0c;也就是只对相同类别的boxes来计算IOU&#xff1b;但现实场景中经常遇到同一个物体被识别成2个类别&#xff0c;也就是模型认为它既是类别1也是类别2.这时候通过多类别nms就过滤不掉这种重叠的框。所以…

Android Studio - adb.exe已停止运作的解决方案

adb.exe 是Android Debug Bridge 的缩写&#xff0c;它是Android SDK 中的一个调试工具&#xff0c;允许开发者通过命令行界面与设备进行交互&#xff0c;执行各种操作&#xff0c;如运行设备的shell、管理模拟器或设备的端口映射、在计算机和设备之间上传/下载文件、将本地APK…

元服务体验-服务发现

服务发现&#xff0c;无论线上或线下的方式都可以发现元服务。 线上&#xff1a;基于用户意图。从精准意图的搜索、用户事件触发的推荐到主动探索等场景。用户可以在设备的负一屏、全局搜索、应用市场、桌面等场景发现元服务。 线下&#xff1a;用户在 HarmonyOS Connect标签…

Flask启动5000端口后关不掉了?

事情是这样的&#xff1a; 使用python app.py启动flask应用后&#xff0c;又启动了另一个flask测试应用&#xff0c;也能启动成功&#xff0c;也没有报设么端口冲突&#xff0c;关闭黑窗口后&#xff0c;访问还是有守护进程在运行&#xff0c; 为什么我知道5000还在运行&#…

转型Web3开发第二课:Dapp开发入门基础 | 01 | 安装MetaMask

前言 完成了《转型 Web3 开发第一课》之后&#xff0c;得到了不少读者的认可&#xff0c;很多都在问什么时候开始下一课&#xff0c;近期终于抽出了时间开始搞起这第二课。 这第二课的主题为「Dapp开发入门基础」&#xff0c;即想要转型做 Dapp 开发的人员&#xff0c;不管是…

01数据结构 - 顺序表

这里是只讲干货不讲废话的炽念&#xff0c;这个系列的文章是为了我自己以后复习数据结构而写&#xff0c;所以可能会用一种我自己能够听懂的方式来描述&#xff0c;不会像书本上那么枯燥和无聊&#xff0c;且全系列的代码均是可运行的代码&#xff0c;关键地方会给出注释^_^ 全…

CSS-0_3 CSS和单位

文章目录 CSS的值和单位属性值长度单位CSS和绝对单位CSS和相对单位百分比em & rem视口 颜色单位 碎碎念 CSS的值和单位 我们知道&#xff0c;CSS是由属性和属性值所组成的表 随着CSS的发展&#xff0c;属性不说几千也有几百&#xff0c;我从来不支持去背诵所有的可能性。…

AWS Aurora Postgres 的开源替代品:存储和计算分离 | 开源日报 No.278

neondatabase/neon Stars: 13.0k License: Apache-2.0 Neon 是一个无服务器的开源替代品&#xff0c;用于 AWS Aurora Postgres。它将存储和计算分离&#xff0c;通过在节点集群中重新分配数据来替换 PostgreSQL 存储层。 提供自动扩展、分支和无限存储。Neon 安装包括计算节…

图解PyTorch中的Transpose操作

在PyTorch中&#xff0c;我们时常会对张量进行转置操作。若张量是二维的&#xff0c;则非常容易理解。若张量维度更高&#xff0c;则会令人摸不到头脑。 高维张量究竟是怎么转置的&#xff1f;简单来说&#xff0c;就是将参与转置的维度抽出来&#xff0c;将内侧的子张量视为一…

设计模式学习(二)工厂模式——抽象工厂模式

设计模式学习&#xff08;二&#xff09;工厂模式——抽象工厂模式 背景抽象工厂模式优点与缺点参考文章 背景 现在我需要开发一个相机操作模块&#xff0c;它可能在Windows下运行&#xff0c;也可能在Linux下运行。由于在厂家提供的SDK中&#xff0c;Windows下的SDK和Linux下…

DROO论文笔记

推荐文章DROO源码及论文学习 读论文《Deep Reinforcement Learning for Online Computation Offloading in Wireless Powered Mobile-Edge Computing Networks》的笔记 论文地址&#xff1a;用于无线移动边缘计算网络在线计算卸载的深度强化学习 论文代码地址&#xff1a;DR…

统计学9——分类数据统计

知识结构 内容精读 1.分类数据与$\chi^2$统计量 分类数据在第一章已经进行了详细介绍&#xff0c;就是对数据进行分类的结果&#xff0c;特征是&#xff0c;调查结果虽然用数值表示&#xff0c;但不同数值描述了调查对象的不同特征。由此分类数据的结果是频数&#xff0c;而$…

git链接远程仓库

【 一 】ssh链接远程仓库 删除git仓库 【 1 】初步使用方法 1、之前把本地代码&#xff0c;以https形式&#xff0c;提交到了远程仓库 # - git remote add origin https://gitee.com/bai-zhitao/lufy.git- 输入用户名密码2、ssh认证&#xff0c;只需要配置一次&#xff…

uniapp踩坑之项目:uni-table垂直居中和水平居中

uni-table 中的水平居中uni-td align"center"&#xff0c;css里的属性vertical-align: middle //html 水平居中<uni-table ref"table" :loading"loading" border stripe emptyText"暂无更多数据"><uni-tr><uni-th :wid…

车载音视频MediaPlayer优化方案

媒体播放现状 从手机到车载&#xff0c;在很多地方还是有很大的不同。针对多媒体的场景Android车机目前大部分结构大致结构如下图&#xff1a; 从以上图看出的问题&#xff1a; 各个音视频APP单独实现播控界面&#xff0c;播放链路不一致&#xff0c;使用的底层播放器和音频焦…