精华内容
下载资源
问答
  • Mybatis的一级缓存和二级缓存详解

    千次阅读 多人点赞 2018-11-19 19:11:59
    注:本笔记是根据尚硅谷的MyBatis视频记录的 对于任何一个持久层框架,都有缓存机制;... 两个关于mybatis缓存额外的链接: ...关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行...

    注:本笔记是根据尚硅谷的MyBatis视频记录的

    对于任何一个持久层框架,都有缓存机制;缓存在电脑中有一块真实的存储空间(https://baike.baidu.com/item/%E7%BC%93%E5%AD%98/100710?fr=aladdin);

    两个关于mybatis缓存额外的链接:

    关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行顺序

     Mybatis整合第三方缓存步骤具体可参考:Mybatis整合第三方缓存ehcache

    缓存原理图:

    一、一级缓存(本地缓存)

    sqlSession级别的缓存。(相当于一个方法内的缓存)

    每一次会话都对应自己的一级缓存,作用范围比较小,一旦会话关闭就查询不到了;

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

    测试:

    取缓存中的数据:

    说明:当再次查询发现已经有数据了,就直接在缓存中返回之前查的数据,而不再访问数据库;

    二、一级缓存失效的四种情况:

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

    1、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);
    		
    		//sqlSession相同,查询条件不同
    		Employee emp03 = mapper.getEmpById(3);
    		System.out.println(emp03);
    		System.out.println(emp01==emp03);
    
    	}finally{
    		openSession.close();
    	}
    }

    结果:

    说明:当缓存中没有数据时,会重新查数据库

    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);
    		
    		//sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
    		mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
    		System.out.println("数据添加成功");
    		
    		Employee emp02 = mapper.getEmpById(1);
    		System.out.println(emp02);
    		System.out.println(emp01==emp02);
    		
    	}finally{
    		openSession.close();
    	}
    }

    结果:

    说明:为了防止增删改对当前数据的影响,即使查的同一个对象,也会重新查数据库

    原因:每个增删改标签都有默认清空缓存配置:flushCache="true",不过这是默认的是一级和二级缓存都清空

    4、sqlsession相同,但手动清楚了一级缓存(缓存清空)

    清空缓存:openSession.clearCache();

    @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相同,手动清除了一级缓存(缓存清空)
    		openSession.clearCache();
    		
    		Employee emp02 = mapper.getEmpById(1);
    		System.out.println(emp02);
    		System.out.println(emp01==emp02);
    		
    	}finally{
    		openSession.close();
    	}
    }

    结果:

    说明:手动清空缓存后,需要重新查数据库

    三、二级缓存(全局缓存)

    基于namespace名称空间级别的缓存:一个namespace对应一个二级缓存

    即一个mapper.xml对应一个缓存:

    1、工作机制:

    *         1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    *         2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
    *         3、sqlSession===EmployeeMapper==>Employee
    *                         DepartmentMapper===>Department
    *             不同namespace查出的数据会放在自己对应的缓存中(map)
    *             效果:数据会从二级缓存中获取
    *                 查出的数据都会被默认先放在一级缓存中。
    *                 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

    2、 使用:
    *             1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
    *             2)、去mapper.xml中配置使用二级缓存:
    *                 <cache></cache>
    *             3)、我们的POJO需要实现序列化接口

    1)在mybatis全局配置文件中开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>

    2)在mapper.xml中配置使用二级缓存

    直接加上: <cache><cache/>

    <?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.atguigu.mybatis.dao.EmployeeMapper">
    	
    	 <cache><cache/>
    
     	<!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     	<select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
     		select * from tbl_employee where last_name like #{lastName}
     	</select>
    </mapper>

    或者 <cache>中配置一些参数:

    eviction:缓存的回收策略:
        • LRU – 最近最少使用的:移除最长时间不被使用的对象。
        • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
        • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
        • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
        • 默认的是 LRU。
    flushInterval:缓存刷新间隔
        缓存多长时间清空一次,默认不清空,设置一个毫秒值
    readOnly:是否只读:
        true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
                 mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
        false:非只读:mybatis觉得获取的数据可能会被修改。
                mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
    size:缓存存放多少元素;
    type="":指定自定义缓存的全类名;
            实现Cache接口即可;

    <?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.atguigu.mybatis.dao.EmployeeMapper">
    
    	 <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> 
     
     	<!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     	<select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
     		select * from tbl_employee where last_name like #{lastName}
     	</select>
    </mapper>

    3)POJO需要实现序列化接口

    测试:

    注意:需要openSession.close();后,才能从二级缓存中查数据;

    @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 = openSession2.getMapper(EmployeeMapper.class);
    		
    		Employee emp01 = mapper.getEmpById(1);
    		System.out.println(emp01);
    		openSession.close();
    		
    		//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
    		Employee emp02 = mapper2.getEmpById(1);
    		System.out.println(emp02);
    		openSession2.close();
    		
    	}finally{
    		
    	}
    }

    结果:

    说明:第二次查询是从二级缓存中拿到的数据,并没有发送新的sql;

    注意:

    如果openSession.close();在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据:

    @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 = openSession2.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{
    		
    	}
    }

    结果:

    说明:第二次又重新发送了sql,因为从二级缓存中取数据时,会话没关闭所以二级缓存中没数据,所以又去一级缓存中查询,也没有数据则发送了sql查数据库;

    所以,只有会话关闭或提交后,一级缓存中的数据才会转移到二级缓存中,然后因为是同一个namespace所以可以获取到数据;

    关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行顺序

    四、和缓存有关的设置/属性

    1)、mybatis全局配置文件中配置全局缓存开启和清空

    1.1)控制二级缓存的开启和关闭

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

             cacheEnabled=true:开启缓存;false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)

    1.2)控制一级缓存的开启和关闭

             <setting name="localCacheScope" value="SESSION"/>

             localCacheScope:本地缓存作用域(一级缓存SESSION);

             当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存;

    注意:一级缓存关闭后,二级缓存自然也无法使用;  

    2)、方法中sqlSession清除缓存测试

    sqlSession.clearCache();只是清除当前session的一级缓存;

    如果openSession清空了缓存,即执行了openSession.clearCache()方法:

    结果:

     说明:openSession清空缓存不影响二级缓存;只清空了一级缓存;因为在openSession.close()时,就将一级缓存保存至了二级缓存; 

    3)、mapper.xml中也可以配置一级和二级缓存开启和使用

    3.1)每个select标签都默认配置了useCache="true":
             如果useCache= false:则表示不使用缓存(一级缓存依然使用,二级缓存不使用)
    3.2)每个增删改标签默认配置了flushCache="true":(一级二级都会清除)


    增删改执行完成后就会清除缓存;

    测试:默认flushCache="true":一级缓存和二级缓存都会被清空;

    执行增加操作:

    结果:

    注意:查询标签<select>默认flushCache="false":如果flushCache=true;每次查询之后都会清空缓存;一级和二级缓存都无法使用;

    五、整合第三方缓存

    mybatis通过map实现的缓存,很不专业;此时可以通过整合第三方缓存来达到缓存的目的;

    做法:实现Cache.java接口中的方法即可:

    如实现putObject()方法,往缓存中写数据;实现getObject()方法,从缓存中获取数据;至于什么时候调用,由mybatis决定;

     具体可参考:Mybatis整合第三方缓存ehcache

    展开全文
  • MyBatis中的一级缓存和二级缓存介绍

    千次阅读 多人点赞 2017-06-13 20:07:22
    先说缓存,合理使用缓存是优化...一级缓存 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也
    先说缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。

    一级缓存
    一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。

    一级缓存的工作原理:


    用户发起查询请求,查找某条数据,sqlSession先去缓存中查找,是否有该数据,如果有,读取;
    如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。
    但sqlSession执行commit,即增删改操作时会清空缓存。这么做的目的是避免脏读。
    如果commit不清空缓存,会有以下场景:A查询了某商品库存为10件,并将10件库存的数据存入缓存中,之后被客户买走了10件,数据被delete了,但是下次查询这件商品时,并不从数据库中查询,而是从缓存中查询,就会出现错误。
    既然有了一级缓存,那么为什么要提供二级缓存呢?
    二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的作用范围更大。
    还有一个原因,实际开发中,MyBatis通常和Spring进行整合开发。Spring将事务放到Service中管理,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。

    二级缓存
    二级缓存原理:

    二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
    UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
    开启二级缓存:
    1,打开总开关
    在MyBatis的配置文件中加入:
    <settings>  
       <!--开启二级缓存-->  
        <setting name="cacheEnabled" value="true"/>  
    </settings> 
    2,在需要开启二级缓存的mapper.xml中加入caceh标签
    <cache/>
    3,让使用二级缓存的POJO类实现Serializable接口
    public class User implements Serializable {}

    测试一下
    @Test
    public void testCache2() throws Exception {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = userMapper1.findUserById(1);
        System.out.println(user1);
        sqlSession1.close();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.findUserById(1);
        System.out.println(user2);
        sqlSession2.close();
    }
    输出结果:
    DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.0
    DEBUG [main] - Opening JDBC Connection
    DEBUG [main] - Created connection 103887628.
    DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
    DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id=? 
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <==      Total: 1
    User [id=1, username=张三, sex=1, birthday=null, address=null]
    DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
    DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
    DEBUG [main] - Returned connection 103887628 to pool.
    DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.5
    User [id=1, username=张三, sex=1, birthday=null, address=null]
    我们可以从打印的信息看出,两个sqlSession,去查询同一条数据,只发起一次select查询语句,第二次直接从Cache中读取。

    前面我们说到,Spring和MyBatis整合时, 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。那么如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。

    总结:
    对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。

    但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
    二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
    举个简单的例子:
    订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。

    根据以上,想要使用二级缓存时需要想好两个问题:
    1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
    2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。





    展开全文
  • MyBatis【四】一级缓存和二级缓存

    千次阅读 多人点赞 2020-04-22 10:18:33
    Mybatis中的一级缓存和二级缓存: 【一级缓存】 它指的是Mybatis中SqlSession对象的缓存 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。 当我们再次查询...

    MyBatis缓存

    • 一级缓存默认是开启的
    • 二级缓存在映射配置文件中开启

    Mybatis中的一级缓存和二级缓存:

    【一级缓存】

    它指的是Mybatis中SqlSession对象的缓存

    • 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。

    • 当我们再次查询同样的数据,Mybatis会先去SqlSession中查询是否有,有的话直接拿出来用。

    • 当SqlSession对象消失时,Mybatis的一级缓存也就消失了。

    【二级缓存】

    它指的是Mybatis中SqlSessionFactory对象的缓存。

    由同一个SqlSessionFactory对象创建的SqlSession共享其缓存

    二级缓存的使用步骤:

    1. 让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
    2. 让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
    3. 让当前的操作支持二级缓存(在select标签中配置)

    1. 一级缓存

    主配置文件SqlMapConfig.xml中开启缓存(默认是开启的)


    MyBatis提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。


    Mybatis的一级缓存的作用域是session,是SqlSession级别的缓存,只要SqlSession没有flushclose,它就存在。

    如果执行相同的SQL(相同语句和参数), MyBatis不进行执行SQL,而是从缓存中命中返回查询;如果命中直接返回,没有命中则执行SQL,从数据库中査询

    一级缓存存在测试


    我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供给我们的一级缓起作用了。因为一级缓存的存在,导致第二次查询id为51的记录时,并没有发出SQL语句从数据库中查询数据,而是从一级缓存中查询。

    一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

    一级缓存清空测试


    当执行sqlSession.close()后,再次获取sqlSession并查询id=51的User对象时,又重新执行了SQL 语句,从数据库进行了查询操作。

    2. 二级缓存

    MyBatis 的二级缓存是mapper映射级别的缓存,作用域是同一个mapper的namespace ,同一个namespace中查询SQL可以从缓存中命中,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。



    二级缓存测试

    映射配置文件IUserDao.xml中开启二级缓存

    开启user支持二级缓存
    <cache/>
    


    展开全文
  • Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接...

    Mybatis介绍之缓存

    Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存1024条SQL。二级缓存是指可以跨SqlSession的缓存。

     

           Mybatis中进行SQL查询是通过org.apache.ibatis.executor.Executor接口进行的,总体来讲,它一共有两类实现,一类是BaseExecutor,一类是CachingExecutor。前者是非启用二级缓存时使用的,而后者是采用的装饰器模式,在启用了二级缓存时使用,当二级缓存没有命中时,底层还是通过BaseExecutor来实现的。

     

    8.1     一级缓存

           一级缓存是默认启用的,在BaseExecutor的query()方法中实现,底层默认使用的是PerpetualCache实现,PerpetualCache采用HashMap存储数据。一级缓存会在进行增、删、改操作时进行清除。

     

      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSqlthrows SQLException {

        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());

        if (closed) {

          throw new ExecutorException("Executor was closed.");

        }

        if (queryStack == 0 && ms.isFlushCacheRequired()) {

          clearLocalCache();

        }

        List<E> list;

        try {

          queryStack++;

          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

          if (list != null) {

            handleLocallyCachedOutputParameters(mskeyparameterboundSql);

          } else {

            list = queryFromDatabase(msparameterrowBoundsresultHandlerkeyboundSql);

          }

        } finally {

          queryStack--;

        }

        if (queryStack == 0) {

          for (DeferredLoad deferredLoad : deferredLoads) {

            deferredLoad.load();

          }

          // issue #601

          deferredLoads.clear();

          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

            // issue #482

            clearLocalCache();

          }

        }

        return list;

      }

     

           一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION,如果我们不需要使用一级缓存,那么我们可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper语句后都会将一级缓存清除。如果只是需要对某一条select语句禁用一级缓存,则可以在对应的select元素上加上flushCache="true"。如果需要更改一级缓存的范围,请在Mybatis的配置文件中,在<settings>下通过localCacheScope指定。

          <setting name="localCacheScope" value="SESSION"/>

     

           为了验证一级缓存,我们进行如下测试,在testCache1中,我们通过同一个SqlSession查询了两次一样的SQL,第二次不会发送SQL。在testCache2中,我们也是查询了两次一样的SQL,但是它们是不同的SqlSession,结果会发送两次SQL请求。需要注意的是当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,如有需要可以启用二级缓存。而在同一个事务中时共用的就是同一个SqlSession。这块有兴趣的朋友可以去查看MapperFactoryBean的源码,其父类SqlSessionDaoSupport在设置SqlSessionFactory或设置SqlSessionTemplate时的逻辑。

       /**

        * 默认是有一级缓存的,一级缓存只针对于使用同一个SqlSession的情况。<br/>

        * 注意:当使用Spring整合后的Mybatis,不在同一个事务中的Mapper接口对应的操作也是没有一级缓存的,因为它们是对应不同的SqlSession。在本示例中如需要下面的第二个语句可使用一级缓存,需要testCache()方法在一个事务中,使用@Transactional标注。

        */

       @Test

       public void testCache() {

          PersonMapper mapper = session.getMapper(PersonMapper.class);

          mapper.findById(5L);

          mapper.findById(5L);

       }

      

       @Test

       public void testCache2() {

          SqlSession session1 = this.sessionFactory.openSession();

          SqlSession session2 = this.sessionFactory.openSession();

          session1.getMapper(PersonMapper.class).findById(5L);

          session2.getMapper(PersonMapper.class).findById(5L);

       }

     

    8.2     二级缓存

    8.2.1简介

           二级缓存是默认启用的,如想取消,则可以通过Mybatis配置文件中的<settings>元素下的子元素<setting>来指定cacheEnabled为false。

       <settings>

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

       </settings>

     

           cacheEnabled默认是启用的,只有在该值为true的时候,底层使用的Executor才是支持二级缓存的CachingExecutor。具体可参考Mybatis的核心配置类org.apache.ibatis.session.Configuration的newExecutor方法实现。

      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

        executorType = executorType == null ? defaultExecutorType : executorType;

        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

        Executor executor;

        if (ExecutorType.BATCH == executorType) {

          executor = new BatchExecutor(thistransaction);

        } else if (ExecutorType.REUSE == executorType) {

          executor = new ReuseExecutor(thistransaction);

        } else {

          executor = new SimpleExecutor(thistransaction);

        }

        if (cacheEnabled) {

          executor = new CachingExecutor(executor);

        }

        executor = (Executor) interceptorChain.pluginAll(executor);

        return executor;

      }

     

           要使用二级缓存除了上面一个配置外,我们还需要在我们对应的Mapper.xml文件中定义需要使用的cache,具体可以参考CachingExecutor的以下实现,其中使用的cache就是我们在对应的Mapper.xml中定义的cache。还有一个条件就是需要当前的查询语句是配置了使用cache的,即下面源码的useCache()是返回true的,默认情况下所有select语句的useCache都是true,如果我们在启用了二级缓存后,有某个查询语句是我们不想缓存的,则可以通过指定其useCache为false来达到对应的效果。

      @Override

      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)

          throws SQLException {

        Cache cache = ms.getCache();

        if (cache != null) {

          flushCacheIfRequired(ms);

          if (ms.isUseCache() && resultHandler == null) {

            ensureNoOutParams(msparameterObjectboundSql);

            @SuppressWarnings("unchecked")

            List<E> list = (List<E>) tcm.getObject(cachekey);

            if (list == null) {

              list = delegate.<E> query(msparameterObjectrowBoundsresultHandlerkeyboundSql);

              tcm.putObject(cachekeylist); // issue #578 and #116

            }

            return list;

          }

        }

        return delegate.<E> query(msparameterObjectrowBoundsresultHandlerkeyboundSql);

      }

     

    8.2.2cache定义

           刚刚说了我们要想使用二级缓存,是需要在对应的Mapper.xml文件中定义其中的查询语句需要使用哪个cache来缓存数据的。这有两种方式可以定义,一种是通过cache元素定义,一种是通过cache-ref元素来定义。但是需要注意的是对于同一个Mapper来讲,它只能使用一个Cache,当同时使用了<cache><cache-ref>时使用<cache>定义的优先级更高。Mapper使用的Cache是与我们的Mapper对应的namespace绑定的,一个namespace最多只会有一个Cache与其绑定。

     

    8.2.2.1cache元素定义

           使用cache元素来定义使用的Cache时,最简单的做法是直接在对应的Mapper.xml文件中指定一个空的<cache/>元素,这个时候Mybatis会按照默认配置创建一个Cache对象,准备的说是PerpetualCache对象,更准确的说是LruCache对象(底层用了装饰器模式)。具体可以参考XMLMapperBuilder中的cacheElement()方法中解析cache元素的逻辑。空cache元素定义会生成一个采用最近最少使用算法最多只能存储1024个元素的缓存,而且是可读写的缓存,即该缓存是全局共享的,任何一个线程在拿到缓存结果后对数据的修改都将影响其它线程获取的缓存结果,因为它们是共享的,同一个对象。

     

             cache元素可指定如下属性,每种属性的指定都是针对都是针对底层Cache的一种装饰,采用的是装饰器的模式。

    Ø  blocking:默认为false,当指定为true时将采用BlockingCache进行封装,blocking,阻塞的意思,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。

    Ø  eviction:eviction,驱逐的意思。也就是元素驱逐算法,默认是LRU,对应的就是LruCache,其默认只保存1024个Key,超出时按照最近最少使用算法进行驱逐,详情请参考LruCache的源码。如果想使用自己的算法,则可以将该值指定为自己的驱逐算法实现类,只需要自己的类实现Mybatis的Cache接口即可。除了LRU以外,系统还提供了FIFO(先进先出,对应FifoCache)、SOFT(采用软引用存储Value,便于垃圾回收,对应SoftCache)和WEAK(采用弱引用存储Value,便于垃圾回收,对应WeakCache)这三种策略。

    Ø  flushInterval:清空缓存的时间间隔,单位是毫秒,默认是不会清空的。当指定了该值时会再用ScheduleCache包装一次,其会在每次对缓存进行操作时判断距离最近一次清空缓存的时间是否超过了flushInterval指定的时间,如果超出了,则清空当前的缓存,详情可参考ScheduleCache的实现。

    Ø  readOnly:是否只读,默认为false。当指定为false时,底层会用SerializedCache包装一次,其会在写缓存的时候将缓存对象进行序列化,然后在读缓存的时候进行反序列化,这样每次读到的都将是一个新的对象,即使你更改了读取到的结果,也不会影响原来缓存的对象,即非只读,你每次拿到这个缓存结果都可以进行修改,而不会影响原来的缓存结果;当指定为true时那就是每次获取的都是同一个引用,对其修改会影响后续的缓存数据获取,这种情况下是不建议对获取到的缓存结果进行更改,意为只读。这是Mybatis二级缓存读写和只读的定义,可能与我们通常情况下的只读和读写意义有点不同。每次都进行序列化和反序列化无疑会影响性能,但是这样的缓存结果更安全,不会被随意更改,具体可根据实际情况进行选择。详情可参考SerializedCache的源码。

    Ø  size:用来指定缓存中最多保存的Key的数量。其是针对LruCache而言的,LruCache默认只存储最多1024个Key,可通过该属性来改变默认值,当然,如果你通过eviction指定了自己的驱逐算法,同时自己的实现里面也有setSize方法,那么也可以通过cache的size属性给自定义的驱逐算法里面的size赋值。

    Ø  type:type属性用来指定当前底层缓存实现类,默认是PerpetualCache,如果我们想使用自定义的Cache,则可以通过该属性来指定,对应的值是我们自定义的Cache的全路径名称。

     

    8.2.2.2cache-ref元素定义

           cache-ref元素可以用来指定其它Mapper.xml中定义的Cache,有的时候可能我们多个不同的Mapper需要共享同一个缓存的,是希望在MapperA中缓存的内容在MapperB中可以直接命中的,这个时候我们就可以考虑使用cache-ref,这种场景只需要保证它们的缓存的Key是一致的即可命中,二级缓存的Key是通过Executor接口的createCacheKey()方法生成的,其实现基本都是BaseExecutor,源码如下。

      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {

        if (closed) {

          throw new ExecutorException("Executor was closed.");

        }

        CacheKey cacheKey = new CacheKey();

        cacheKey.update(ms.getId());

        cacheKey.update(Integer.valueOf(rowBounds.getOffset()));

        cacheKey.update(Integer.valueOf(rowBounds.getLimit()));

        cacheKey.update(boundSql.getSql());

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

        // mimic DefaultParameterHandler logic

        for (int i = 0; i < parameterMappings.size(); i++) {

          ParameterMapping parameterMapping = parameterMappings.get(i);

          if (parameterMapping.getMode() != ParameterMode.OUT) {

            Object value;

            String propertyName = parameterMapping.getProperty();

            if (boundSql.hasAdditionalParameter(propertyName)) {

              value = boundSql.getAdditionalParameter(propertyName);

            } else if (parameterObject == null) {

              value = null;

            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {

              value = parameterObject;

            } else {

              MetaObject metaObject = configuration.newMetaObject(parameterObject);

              value = metaObject.getValue(propertyName);

            }

            cacheKey.update(value);

          }

        }

        if (configuration.getEnvironment() != null) {

          // issue #176

          cacheKey.update(configuration.getEnvironment().getId());

        }

        return cacheKey;

      }

     

           打个比方我想在PersonMapper.xml中的查询都使用在UserMapper.xml中定义的Cache,则可以通过cache-ref元素的namespace属性指定需要引用的Cache所在的namespace,即UserMapper.xml中的定义的namespace,假设在UserMapper.xml中定义的namespace是com.elim.learn.mybatis.dao.UserMapper,则在PersonMapper.xml的cache-ref应该定义如下。

       <cache-ref namespace="com.elim.learn.mybatis.dao.UserMapper"/>

     

    8.2.3自定义cache

           前面提到Mybatis的Cache默认会使用PerpetualCache存储数据,如果我们不想按照它的逻辑实现,或者我们想使用其它缓存框架来实现,比如使用Ehcache、Redis等,这个时候我们就可以使用自己的Cache实现,Mybatis是给我们留有对应的接口,允许我们进行自定义的。要想实现自定义的Cache我们必须定义一个自己的类来实现Mybatis提供的Cache接口,实现对应的接口方法。注意,自定义的Cache必须包含一个接收一个String参数的构造方法,这个参数就是Cache的ID,详情请参考Mybatis初始化Cache的过程,对应XMLMapperBuilder的cacheElement()方法。以下是一个简单的MyCache的实现。

    /**

     * @author Elim

     * 20161220

     */

    publicclass MyCache implements Cache {

     

       private String id;

       private String name;//Name,故意加这么一个属性,以方便演示给自定义Cache的属性设值

      

       private Map<Object, Object> cache = new HashMap<Object, Object>();

      

       /**

        * 构造方法。自定义的Cache实现一定要有一个id参数

        * @param id

        */

       public MyCache(String id) {

          this.id = id;

       }

      

       @Override

       public String getId() {

          return this.id;

       }

     

       @Override

       public void putObject(Object key, Object value) {

          this.cache.put(keyvalue);

       }

     

       @Override

       public Object getObject(Object key) {

          return this.cache.get(key);

       }

     

       @Override

       public Object removeObject(Object key) {

          return this.cache.remove(key);

       }

     

       @Override

       public void clear() {

          this.cache.clear();

       }

     

       @Override

       public int getSize() {

          return this.cache.size();

       }

     

       @Override

       public ReadWriteLock getReadWriteLock() {

          return null;

       }

      

       /**

        * @return the name

        */

       public String getName() {

          return name;

       }

     

       /**

        * @param name the name to set

        */

       public void setName(String name) {

          this.name = name;

       }

     

    }

     

           定义了自己的Cache实现类后我们就可以在需要使用它的Mapper.xml文件中通过<cache>标签的type属性来指定我们需要使用的Cache。如果我们的自定义Cache是需要指定参数的,则可以通过<cache>标签的子标签<property>来指定对应的参数,Mybatis在解析的时候会调用指定属性对应的set方法。针对于上面的自定义Cache,我们的配置如下。

       <cache type="com.elim.learn.mybatis.cache.MyCache">

          <property name="name" value="调用setName()方法需要传递的参数值"/>

       </cache>

     

    注意:如果我们使用了自定义的Cache,那么cache标签的其它属性,如size、eviction等都不会对自定义的Cache起作用,也就是说不会自动对自定义的Cache进行包装,如果需要使用自定义的Cache,同时又希望使用Mybatis自带的那些Cache包装类,则可以在自定义的Cache中自己进行包装。


    8.2.4缓存的清除

           二级缓存默认是会在执行update、insert和delete语句时进行清空的,具体可以参考CachingExecutor的update()实现。如果我们不希望在执行某一条更新语句时清空对应的二级缓存,那么我们可以在对应的语句上指定flushCache属性等于false。如果只是某一条select语句不希望使用二级缓存和一级缓存,则也可以在对应的select元素上加上flushCache="true"。

       <insert id="delete" parameterType="java.lang.Long"flushCache="false">

          delete t_person where id=#{id}

       </insert>

     

    8.2.5自己操作Cache

           Mybatis中创建的二级缓存都会交给Configuration进行管理,Configuration类是Mybatis的核心类,里面包含了各种Mybatis资源的管理,其可以很方便的通过SqlSession、SqlSessionFactory获取,如有需要我们可以直接通过它来操作我们的Cache。

       @Test

       public void testGetCache() {

          Configuration configuration = this.session.getConfiguration();

    //    this.sessionFactory.getConfiguration();

          Collection<Cache> caches = configuration.getCaches();

          System.out.println(caches);

       }

     

    8.2.6测试

           针对二级缓存进行了以下测试,获取两个不同的SqlSession执行两条相同的SQL,在未指定Cache时Mybatis将查询两次数据库,在指定了Cache时Mybatis只查询了一次数据库,第二次是从缓存中拿的。

       @Test

       public void testCache2() {

          SqlSession session1 = this.sessionFactory.openSession();

          SqlSession session2 = this.sessionFactory.openSession();

          session1.getMapper(PersonMapper.class).findById(5L);

          session1.commit();

          session2.getMapper(PersonMapper.class).findById(5L);

       }

     

           注意在上面的代码中,我在session1执行完对应的SQL后调用了session1的commit()方法,即提交了它的事务,这样我们在第二次查询的时候才会缓存命中,才不会查询数据库,否则就会连着查询两次数据库。这是因为在CachingExecutor中Mybatis在查询的过程中又在原来Cache的基础上包装了TransactionalCache,这个Cache只会在事务提交后才真正的写入缓存,所以在上面的示例中,如果session1执行完SQL后没有马上commit就紧接着用session2执行SQL,虽然session1查询时没有缓存命中,但是此时写入缓存操作还没有进行,session2再查询的时候也就不会缓存命中了。

     

    参考文档

    http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache

     

    (注:本文是基于Mybatis3.3.1所写,写于2016年12月21日星期三)

    展开全文
  • 上一篇:21-Mybatis二级缓存... 关闭二级缓存,一级缓存可正常使用 2. <select useCache="false"> 关闭二级缓存,一级缓存可正常使用 3. <select flushCache="true"> :一二级均被清空
  • dedecms调用某顶级栏目下二级标签

    千次阅读 2016-12-29 12:45:49
    dedecms调用某顶级栏目下二级标签,调用各个子栏目的推荐文章及详情,适用于子栏目布局不一的设计要求
  • Mybatis一级缓存与二级缓存

    千次阅读 2019-01-01 15:31:16
    mybatis的有两种缓存,一级缓存和二级缓存。两个缓存的不同点相同点总结如下 不同点: 一级缓存存在于一个SqlSession之内,二级缓存存在于不同的SqlSession之间 一级缓存不需要手动开启,属于默认开启状态;...
  • dedecms首页调用一级二级菜单

    千次阅读 2018-11-25 00:16:59
    调用指定id的栏目,不过只能调用个,要想多个调用就只能重复多次调用该标签 {dede:type typeid='栏目id'} <a href="[field:typeurl/]...第种:(没有二级菜单的调用)直接 <div id="navMenu"> <u...
  • 实现一级菜单二级菜单:
  • :mybatis查询缓存基本介绍 缓存:将相同查询条件的sql语句执行遍后所得到的结果存在内存或者某种缓存介质当中,当下次遇到一模一样的查询sql时候不在执行sql与数据库交互,而是直接从缓存中获取结果,减少...
  • Hibernate缓存:一级缓存和二级缓存

    千次阅读 2014-04-08 10:09:16
     缓存是介于物理数据源与应用程序之间,是对数据库中的数据复制份临时放在内存中的容器,其作用是为了减少应用程序对物理数据源访问的次数,从而提高了应用程序的运行性能。Hibernate在进行读取数据的时候,根据...
  • html select标签二级关联

    千次阅读 2018-05-16 18:08:44
    &amp;amp;lt;!DOCTYPE html&amp;amp;gt; &amp;amp;lt;html&amp;amp;gt; &amp;amp;lt;head&amp;... //1.创建维数组用于存储省份城市 var cities = new Array(3);
  • 如图所示,我们可能有时需要同时调取一级栏目和二级栏目做为导航,这在帝国CMS中不是什么难事,用帝国CMS的灵动标签就可以很方便的解决, 程序如下所示,在指定需要读取的父栏目后,就可以把这个栏目下的所有子栏目...
  • mybatis 面试:一级缓存和二级缓存

    千次阅读 2020-02-20 17:14:45
    其中一级缓存默认是开启的,二级缓存是要手动配置开启的, 但是本人这里不建议在实际生产中用mybatis的缓存,还是建议在外部实现自己的缓存,如使用redis等; 一级缓存 1:一级缓存是默认开启的; 2:底层其实是基于...
  • MyBatis实战(5)缓存机制(一级缓存、二级缓存)

    千次阅读 多人点赞 2019-11-30 09:49:01
    这篇博客对Mybatis中的一级缓存和二级缓存做了比较详细的阐述,并有完整的代码实现。
  • 在制作网页并进行布局的时候导航栏是不可缺少的,那么如何设计导航栏的一级标题和二级标题呢?下面这篇文章将详细为您介绍。 以成都大学教务系统的导航栏为例来实现二级菜单的制作: 1.这是我们要制作的一级导航栏:...
  • Select标签下拉列表二级联动级联

    千次阅读 2014-02-20 12:25:04
    首先从服务器端,绑定下拉列表,二级下拉的text命名按照一定规则加上一级下拉的ID。 var options=new Array(); $(document).ready(function(){ //二级联动 $('#ddlPages').children('option').each(function(i...
  • mysql的一级缓存和二级缓存

    千次阅读 2019-08-05 19:10:14
    一级缓存: 也称本地缓存,sqlSession级别的缓存。一级缓存是一直开启的;与数据库同一次回话期间查询到的数据会放在本地缓存中。 如果需要获取相同的数据,直接从缓存中拿,不会再查数据库。 一级缓存失效的四种...
  • Mybatis的一级缓存和二级缓存机制

    千次阅读 2017-04-14 22:02:43
    一级缓存的应用(自动开启,创建Sqlsession) 正式开发,是将mybatisspring进行成开发,事务控制在service中。一个service方法中包括很多mapper方法的调用。 Service 开始执行时, 开启事务(创建sqlSession...
  • mybatis的一级二级缓存

    千次阅读 2019-05-30 20:08:33
    mybatis的一级缓存和二级缓存 mybatis 的一级缓存:sqlsession 级别的,内部维护的其实就是Map,其缓存对象是PerpetualCache。 1、MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类...
  • Hibernate一级缓存和二级缓存的区别

    千次阅读 2018-07-17 23:40:46
    一级缓存 &amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;&amp;...
  • Mybatis 实现原理之 一二级缓存

    千次阅读 2018-10-15 15:19:20
    本片文章讲述的是Mybatis是如何无感知的让用户使用到一二级缓存,以及一二级缓存的实现细节实现原理。 结论:Mybatis 下文通过代码DEMO的展示, 以及源码的解说介绍JDK动态代理, Mybatis对其的应用。 ...
  • Hibernate缓存策略(一级缓存、二级缓存)

    万次阅读 多人点赞 2018-05-16 16:38:52
    Hibernate是个持久化框架,经常需要访问数据库。如果我们能够降低应用程序对物理数据库访问的频次,那会提供应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序运行时先从缓存中读写数据。...
  • mybatis--缓存(一级和二级缓存)

    千次阅读 2017-05-24 22:34:50
    1.缓存 查询数据时将查询结果存放到内存(缓存区)中。 每次查询数据时,先判断缓存区中是否存在数据, ...如果存在,就从缓存区中获取数据 ...失败的一级缓存测试 ※面试相关 [1]SQL语句或查询条件不同
  • DIV+CSS制作二级菜单(横排二级下拉菜单)以及二级菜单出现错位解决方法
  • phpcms 显示某栏目的二级栏目

    千次阅读 2017-02-19 15:49:26
    phpcms 显示某栏目的二级栏目
  • Mybatis一级二级缓存

    万次阅读 多人点赞 2015-06-30 15:07:28
    一级缓存首先做一个测试,创建一个mapper配置文件mapper接口,我这里用了最简单的查询来演示。<mapper namespace="cn.elinzhou.mybatisTest.mapper.UserMapper"> SELEC
  • SQL 查询流程看一二级缓存1.1 创建Executor1.2 CachingExecutor#query1.2.1 二级缓存1.2.1.1 MappedStatement#cache属性创建机制1.2.1.2 cache标签解析1.2.1.3 cacheRef1.2.2 一级缓存2、从SQL更新流程看一二级缓存...
  • mybatis 一级 二级缓存的使用

    千次阅读 2016-10-08 10:27:05
     正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的...
  • 1,对于域名a.a.com域名http://www.a.com/的两个页面 如果要在a.a.com使用AJAX访问http://www.a.com/的时候js 会提示"没有权限" 这样的错误很明显,跨域了,在js当中跨域,怎么解决呢? 发现在页面用Script来...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 332,248
精华内容 132,899
关键字:

一级和二级标签