(十二)Mybatis的入门教程—— 缓存机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Steriles_/article/details/81810428

缓存的作用:提高系统的运行速度,提升查询效率。

mybatis系统中默认定义了两级缓存:一级缓存和二级缓存。

1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。

2、二级缓(也称为全局缓存)存需要手动开启和配置,它是基于namespace级别的缓存。

3、为了提高扩展性。mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

一级缓存:

也称为SqlSession级别的缓存,一级缓存是一直开启的,也没有办法关闭它。一级缓存其实质就是SqlSession级别的一个Map,把所有当前查询出来的数据都放在这个Map当中。然后下次查询的时候,如果还是当前这个SqlSession会话,那么会首先看这个map中有没有需要的数据,如果有则直接拿出来,没有才向数据库中重新查询,这样就能减轻数据库的使用负担了。

对于SqlSession级别的缓存再进行一下解释,一个SqlSession对象有它自己的一级缓存,新的SqlSession对象也拥有它自己的一级缓存,这两个一级缓存之间是不能共用的。所以当前一级缓存中的东西能不能被使用到,也是一个问题。也因此,一级缓存的作用范围就比较小了。

比如用查询举例,假设查询一个部门信息,第一个SqlSession会话查出了部门,那么就会把这个部门信息放在第一个SqlSession会话的一级缓存中。一旦这个SqlSession会话关闭,第二个SqlSession会话再进来想查询这个部门的时候,就得再去数据库重新查询了。而这些部门信息,一旦保存以后,基本是不会去变动的。所以主观上是希望第一次查询的部门信息,在第二次查询的时候可以知道之前有没有查询过,然后把这个信息也放在缓存里,显然这个一级缓存是完成不了的。这边就会用到二级缓存。

一级缓存的作用:

与数据库同一次会话期间查询到的数据会放在本地缓存中;以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。

一级缓存失效情况:没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询

情况1、SqlSession不同。

情况2、SqlSession相同,但查询条件不同。

情况3、SqlSession相同,但两次查询期间执行了增删改操作。

情况4、SqlSession相同,但手动清除了一级缓存。

 

二级缓存:

也称为全局缓存,基于namespace级别的缓存。这个namespace就是xml文件中的名称空间,一个namespace对应一个二级缓存。

二级缓存的工作机制:

1、首先用一个SqlSession会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

2、如果会话关闭,一级缓存中的数据会被保存到二级缓存中。新的SqlSession会话查询信息的话,就可以参照二级缓存中的内容;

3、不同namespace查出的数据会放在自己对应的缓存中,在Mybatis内部就是对应的Map中;

举例来说,如果SqlSession中既有EmployeeMapper查出来的Employee对象,又有DepartmentMapper查询出的Department对象。虽然二级缓存是面向全局的,但是这两个对象会放在不同的Map中,因为二级缓存是基于namespace的,这两个对象的namespace又不一样,所以也不会放在相同的Map中了。

二级缓存的使用步骤:

1、开启二级缓存的全局配置;(显式的指定每个需要更改的配置,即使他是默认的,以防版本更新带来的问题)

<setting name="cacheEnabled" value="true" />

2、去mapper.xml中配置使用二级缓存;

解释一下cache标签中各个属性:

eviction:缓存的回收策略;比如说缓存中内容溢出的时候,把哪些内容给删除掉,包括以下这些类型的对象。默认是LRU。

                LRU——   最近最少使用的;移除最长时间不被使用的对象。

                FIFO——  先进先出;按对象进入缓存的顺序来移除他们。

                SOFT—— 软引用;移除基于垃圾回收器状态和软引用规则的对象。

                WEAK—— 弱引用;更积极地移除基于垃圾收集器状态和弱引用规则的对象。

flushInterval:缓存刷新间隔;即缓存多长时间清空一次,默认是不清空,需要的话,可以设置一个毫秒值。

readOnly:缓存是否只读;默认false。

                true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。

                                    mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,但速度快。

               false:非只读;mybatis觉得获取的数据可能会被修改。

                                        mybatis就会利用序列化和反序列化的技术来克隆一份新的数据给用户。安全,但速度慢。

size:缓存存放多少个元素。

type:自定义缓存的时候,指定缓存全类名。但现在用的是默认的,就可以不用写了。

           如果选择自定义,那么实现Cache接口就可以了,然后把Cache接口的全类名设为type的值就可以了。

<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"  ></cache>

3、POJO需要实现序列化接口;

public class Employee implements Serializable{
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String lastName;
    private String email;
    private String gender;
    private Department dept;
    //省略setter、getter方法

}
public class Department implements Serializable{
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String departmentName;
    private List<Employee> emps;
    //省略setter、getter方法
}

二级缓存使用时的注意事项:

二级缓存的效果是让数据从二级缓存中获取的,但是查出的数据一般都会被默认先放在一级缓存中,然后只有当当前会话关闭或者提交之后,一级缓存中的数据才会转移到二级缓存中

在哪个mapper.xml文件下写了cache标签,那么哪个mapper.xml文件下就有了二级缓存。没写的话,则没有二级缓存了。


一级缓存体验

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
    }finally{
        openSession.close();
    }
}

现在再添加一次查询

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);

        Employee emp02 = mapper.getEmpById(1);
        System.out.println(emp02);
    }finally{
        openSession.close();
    }
}

发现,虽然查询出来的是两个结果,但是sql语句只调用了一次。这就说明了,第二次查询并没有向数据库中查找,因为第二次要用的数据跟第一次查询出来的结果一样,就直接从缓存中得到了。

用以下语句进行证明

System.out.println(emp01==emp02);

结果证明,这两个对象是相等的


一级缓存失效的情况1:SqlSession不同。

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        
        SqlSession openSession2 = sqlSessionFactory.openSession();
        EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession2.close();
        System.out.println(emp01==emp02);
    }finally{
        openSession.close();
    }
}

在两次查询中,都向数据库进行访问了,把查询到的结果放到不同的SqlSession对象中,故而值相同但这两个对象并不相等。

一级缓存失效的情况2:SqlSession相同,但查询条件不同。

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        
       
        Employee emp02 = mapper2.getEmpById(3);
        System.out.println(emp02);

        System.out.println(emp01==emp02);
    }finally{
        openSession.close();
    }
}

第二次查询,必然是要重新向数据库进行查询的,查询完1号员工之后,可当前一级缓存中还没有3号员工的数据呀,所以还是有两条sql语句的。

一级缓存失效的情况3:SqlSession相同,但两次查询期间执行了增删改操作。

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        mapper.addEmp(new Employee(null,"testCache","cache","1"));
        System.out.println("数据添加成功")
       
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);

        System.out.println(emp01==emp02);
    }finally{
        openSession.close();
    }
}

失效的原因:在两次查询之间增减了增删改操作,可能会对当前数据有影响。就比如说,如果两个查询之间把这个数据删了或者改掉了,那不就是查询结果不一样了吗。就算是mybatis也是如此,执行了增删改之后,两次查询都还是会重新向数据库进行访问的。

一级缓存失效的情况4:SqlSession相同,但手动清除了一级缓存。

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        openSession.clearCache();       

        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);

        System.out.println(emp01==emp02);
    }finally{
        openSession.close();
    }
}

清理了缓存之后,当然需要重新向数据库中进行访问啦,缓存里已经没有原先的1号员工的数据了。


二级缓存体验

根据前面的配置,可以直接测试

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        openSession.close();       
 
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession2.close();       
    }finally{
 
    }
}

可以发现,第二次查询是从二级缓存中拿到的数据,并没有发送新的sql语句。因为第一次会话就已经查出了1号员工,然后这和会话虽然关闭了,但是这个1号员工查出的数据就保存在EmployeeMapper对应的缓存中。第二次还是用EmployeeMapper来查询1号员工,故而它就直接引用缓存中的数据就行了。

其中有两条缓存命中率的语句,如下图

一个命中率为0.0表示在缓存取第一次,没有取到想到的值,成功0次;命中率为0.5则表示第二次在缓存中取到了所需值,成功了一次。

二级缓存注意事项的测试,把第一次会话的关闭放在所有查询之后,那么效果则是发送两次sql。

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);  
 
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession.close();     
        openSession2.close();       
    }finally{
 
    }
}

所以一定要先提交或关闭会话,才能把数据从一级缓存中提起出放到二级缓存中去。

猜你喜欢

转载自blog.csdn.net/Steriles_/article/details/81810428
今日推荐