MyBatis框架学习笔记(四):动态SQL语句、映射关系和缓存
创始人
2024-12-28 04:04:42
0

1 动态 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 findMonsterByAge(@Param(value = "age") Integer age);

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

 

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

@Test public void findMonsterByAge() {     List 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 findMonsterByIdAndName(Monster monster);

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

 

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

@Test public void findMonsterByIdAndName(){     Monster monster = new Monster();     monster.setAge(20);     monster.setName("狐狸精");     List 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 findMonsterByIdOrName_choose(Map map);

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

 

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

@Test public void findMonsterByIdOrName_choose(){     HashMap map = new HashMap<>();     map.put("age",-1);     map.put("name","");     List 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 findMonsterById_forEach(Map map)

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

 

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

@Test public void findMonsterById_forEach() {      Map map = new HashMap<>();     map.put("ids", Arrays.asList(5, 6, 7));      List 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 findMonsterByName_Trim(Map map);

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

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

 

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

@Test public void findMonsterByName_Trim() {     Map map = new HashMap<>();     map.put("name", "狐狸精-100");     map.put("age", 10);      List 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 map);

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

      UPDATE `monster`                           `age` = #{age} ,                               `email` = #{email} ,                               `name` = #{name} ,                               `birthday` = #{birthday} ,                               `salary` = #{salary} ,                               `gender` = #{gender} ,                   WHERE id = #{id} 

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

@Test public void updateMonster_set() {     Map 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); } 
         

  (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);   } 
                                                                                                           

 (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);
                       

(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体现这个关系     private List 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 getPets() {         return pets;     }      public void setPets(List 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 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

                                                              

(5) 创建 PetMapper.xml

                                                               

 (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 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 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 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 配置中开启二级缓存 

                                                          。。。。。 
(2)使用二级缓存时 entity 类实现序列化接口 (serializable),因为二级缓存可能使用到序 列化技术(大部分情况用不到,一些第三方缓存库可能用得到,所以可以不配置,等需要的时候再配置也可以)
public class Monster implements Serializable {

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

                  。。。。。。。 

(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)理解二级缓存策略的参数

上面的配置意思如下:

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

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

(2)四大策略

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

(3)如何禁用二级缓存

1) 在 mybatis-config.xml

    

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

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

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

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

     UPDATE mybatis_monster SET NAME=#{name},age=#{age} WHERE id=#{id} 

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

                   net.sf.ehcache         ehcache-core         2.6.11                        org.slf4j         slf4j-api         1.7.25                        org.mybatis.caches         mybatis-ehcache         1.2.1      

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

                   

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

                                  

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

 

(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 

相关内容

热门资讯

第3个了解!来玩app德州软件... 第3个了解!来玩app德州软件透明挂辅助软件,wpk被系统针对(果真有挂)-哔哩哔哩;1、这是跨平台...
七分钟了解!德州云扑克外挂透明... 自定义新版德州云扑克系统规律,只需要输入自己想要的开挂功能,一键便可以生成出德州云扑克专用辅助器,不...
四分钟了解!AAPoKer外挂... 四分钟了解!AAPoKer外挂透明挂辅助神器,wpk长期盈利打法(有挂解密)-哔哩哔哩是一款可以让一...
第9实锤!线上wpk外挂辅助插... 第9实锤!线上wpk外挂辅助插件,微扑克app靠谱的(有挂规律)-哔哩哔哩是一款可以让一直输的玩家,...
第三了解!wepoke软件透明... 第三了解!wepoke软件透明挂辅助作弊,wepoke科技(有挂方法)-哔哩哔哩是一款可以让一直输的...
第4个了解!德州菠萝外挂透明挂... 第4个了解!德州菠萝外挂透明挂辅助器开挂,wepoke ai代打(有挂猫腻)-哔哩哔哩;1、点击下载...
七分钟了解!德州ai外挂辅助神... 七分钟了解!德州ai外挂辅助神器,wpk辅助挂(有挂规律)-哔哩哔哩;1、超多福利:超高返利,海量正...
第4了解!wpk外挂辅助插件,... 自定义新版wpk外挂系统规律,只需要输入自己想要的开挂功能,一键便可以生成出wpk外挂专用辅助器,不...
第两个了解!德州游戏外挂透明挂... 第两个了解!德州游戏外挂透明挂辅助脚本,微扑克ai软件(证实有挂)-哔哩哔哩是一款可以让一直输的玩家...
appium2.0 执行脚本遇... 遇到的问题: appium 上的日志信息: 配置信息方法一之前用1.0的...