精华内容
下载资源
问答
  • Hibernate 级缓存和二级缓存区别
  • MyBatis级缓存和二级缓存

    万次阅读 多人点赞 2018-09-05 22:44:30
    MyBatis自带的缓存有级缓存和二级缓存 级缓存 Mybatis的级缓存是指Session缓存。级缓存的作用域默认是个SqlSession。Mybatis默认开启级缓存。 也就是在同个SqlSession中,执行相同的查询SQL,第次...

    MyBatis自带的缓存有一级缓存和二级缓存

    一级缓存

    Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。
    也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;
    第二次以后是直接去缓存中取。
    当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空。

    一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION,如果不想使用一级缓存,可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper中的语句后都会将一级缓存清除。
    如果需要更改一级缓存的范围,可以在Mybatis的配置文件中,在下通过localCacheScope指定。

    1

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

    建议不需要修改

    需要注意的是
    当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,但是在同一个事务中时共用的是同一个SqlSession。
    如有需要可以启用二级缓存。

    二级缓存

    Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。

    二级缓存是默认启用的(要生效需要对每个Mapper进行配置),如想取消,则可以通过Mybatis配置文件中的元素下的子元素来指定cacheEnabled为false。

    1

    2

    3

    <settings>

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

    </settings>

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

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    ...

        public Executor newExecutor(Transaction transaction) {

            return this.newExecutor(transaction, this.defaultExecutorType);

        }

     

        public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

            executorType = executorType == null this.defaultExecutorType : executorType;

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

            Object executor;

            if (ExecutorType.BATCH == executorType) {

                executor = new BatchExecutor(this, transaction);

            else if (ExecutorType.REUSE == executorType) {

                executor = new ReuseExecutor(this, transaction);

            else {

                executor = new SimpleExecutor(this, transaction);

            }

     

            if (this.cacheEnabled) {//设置为true才执行的

                executor = new CachingExecutor((Executor)executor);

            }

     

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

            return executor;

        }

    ...

    要使用二级缓存除了上面一个配置外,我们还需要在我们每个DAO对应的Mapper.xml文件中定义需要使用的cache

    1

    2

    3

    4

    5

    ...

    <mapper namespace="...UserMapper">

        <cache/><!-- 加上该句即可,使用默认配置、还有另外一种方式,在后面写出 -->

        ...

    </mapper>

    具体可以看org.apache.ibatis.executor.CachingExecutor类的以下实现
    其中使用的cache就是我们在对应的Mapper.xml中定义的cache。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

        BoundSql boundSql = ms.getBoundSql(parameterObject);

        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);

        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

    }

     

    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) {//第一个条件 定义需要使用的cache 

            this.flushCacheIfRequired(ms);

            if (ms.isUseCache() && resultHandler == null) {//第二个条件 需要当前的查询语句是配置了使用cache的,即下面源码的useCache()是返回true的  默认是true

                this.ensureNoOutParams(ms, parameterObject, boundSql);

                List<E> list = (List)this.tcm.getObject(cache, key);

                if (list == null) {

                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

                    this.tcm.putObject(cache, key, list);

                }

     

                return list;

            }

        }

     

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

    }

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

    1

    2

    3

    4

    5

    6

    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" useCache="false">

            select

            <include refid="Base_Column_List"/>

            from tuser

            where id = #{id,jdbcType=VARCHAR}

        </select>

    cache定义的两种使用方式

    上面说了要想使用二级缓存,需要在每个DAO对应的Mapper.xml文件中定义其中的查询语句需要使用cache来缓存数据的。
    这有两种方式可以定义,一种是通过cache元素定义,一种是通过cache-ref元素来定义。
    需要注意的是
    对于同一个Mapper来讲,只能使用一个Cache,当同时使用了和时,定义的优先级更高(后面的代码会给出原因)。
    Mapper使用的Cache是与我们的Mapper对应的namespace绑定的,一个namespace最多只会有一个Cache与其绑定。

    cache元素定义

    使用cache元素来定义使用的Cache时,最简单的做法是直接在对应的Mapper.xml文件中指定一个空的元素(看前面的代码),这个时候Mybatis会按照默认配置创建一个Cache对象,准备的说是PerpetualCache对象,更准确的说是LruCache对象(底层用了装饰器模式)。
    具体的可看org.apache.ibatis.builder.xml.XMLMapperBuilder中的cacheElement()方法解析cache元素的逻辑。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    ...

        private void configurationElement(XNode context) {

            try {

                String namespace = context.getStringAttribute("namespace");

                if (namespace.equals("")) {

                    throw new BuilderException("Mapper's namespace cannot be empty");

                else {

                    this.builderAssistant.setCurrentNamespace(namespace);

                    this.cacheRefElement(context.evalNode("cache-ref"));

                    this.cacheElement(context.evalNode("cache"));//执行在后面

                    this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));

                    this.resultMapElements(context.evalNodes("/mapper/resultMap"));

                    this.sqlElement(context.evalNodes("/mapper/sql"));

                    this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

                }

            catch (Exception var3) {

                throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);

            }

        }

    ...

        private void cacheRefElement(XNode context) {

            if (context != null) {

                this.configuration.addCacheRef(this.builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));

                CacheRefResolver cacheRefResolver = new CacheRefResolver(this.builderAssistant, context.getStringAttribute("namespace"));

     

                try {

                    cacheRefResolver.resolveCacheRef();

                catch (IncompleteElementException var4) {

                    this.configuration.addIncompleteCacheRef(cacheRefResolver);

                }

            }

     

        }

     

        private void cacheElement(XNode context) throws Exception {

            if (context != null) {

                String type = context.getStringAttribute("type""PERPETUAL");

                Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);

                String eviction = context.getStringAttribute("eviction""LRU");

                Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);

                Long flushInterval = context.getLongAttribute("flushInterval");

                Integer size = context.getIntAttribute("size");

                boolean readWrite = !context.getBooleanAttribute("readOnly"false).booleanValue();

                Properties props = context.getChildrenAsProperties();

                this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);

    //如果同时存在<cache>和<cache-ref>,这里的设置会覆盖前面的cache-ref的缓存

            }

     

        }

    空cache元素定义会生成一个采用最近最少使用算法最多只能存储1024个元素的缓存,而且是可读写的缓存,即该缓存是全局共享的,任何一个线程在拿到缓存结果后对数据的修改都将影响其它线程获取的缓存结果,因为它们是共享的,同一个对象。

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

    1. blocking:默认为false,当指定为true时将采用BlockingCache进行封装,blocking,阻塞的意思,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。
      简单理解,也就是设置true时,在进行增删改之后的并发查询,只会有一条去数据库查询,而不会并发

       

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

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

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

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

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

    cache-ref元素定义

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

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

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

        if (this.closed) {

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

        else {

            CacheKey cacheKey = new CacheKey();

            cacheKey.update(ms.getId());

            cacheKey.update(rowBounds.getOffset());

            cacheKey.update(rowBounds.getLimit());

            cacheKey.update(boundSql.getSql());

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

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

     

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

                ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);

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

                    String propertyName = parameterMapping.getProperty();

                    Object value;

                    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 = this.configuration.newMetaObject(parameterObject);

                        value = metaObject.getValue(propertyName);

                    }

     

                    cacheKey.update(value);

                }

            }

     

            return cacheKey;

        }

    }

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

    1

    <cache-ref namespace="cn.chenhaoxiang.dao.UserMapper"/>

    这样这两个Mapper就共享同一个缓存了

    自定义cache就不介绍了。

    测试二级缓存

    查询测试

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    <br />/**

     * Created with IntelliJ IDEA.

     * User: 陈浩翔.

     * Date: 2018/1/10.

     * Time: 下午 10:15.

     * Explain:

     */

    @RunWith(SpringJUnit4ClassRunner.class)

    //配置了@ContextConfiguration注解并使用该注解的locations属性指明spring和配置文件之后

    @ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring-mybatis.xml"})

    public class MyBatisTestBySpringTestFramework {

     

        //注入userService

        @Autowired

        private UserService userService;

     

        @Test

        public void testGetUserId(){

            String userId = "4e07f3963337488e81716cfdd8a0fe04";

            User user = userService.getUserById(userId);

            System.out.println(user);

     

     

            //前面说到spring和MyBatis整合

            User user2 = userService.getUserById(userId);

            System.out.println("user2:"+user2);

        }

    }

    接下来我们把Mapper中的cache元素删除,不使用二级缓存

    再运行测试

    对二级缓存进行了以下测试,获取两个不同的SqlSession(前面有说,Spring和MyBatis集成,每次都是不同的SqlSession)执行两条相同的SQL,在未指定Cache时Mybatis将查询两次数据库,在指定了Cache时Mybatis只查询了一次数据库,第二次是从缓存中拿的。

    Cache Hit Ratio 表示缓存命中率。
    开启二级缓存后,每执行一次查询,系统都会计算一次二级缓存的命中率。
    第一次查询也是先从缓存中查询,只不过缓存中一定是没有的。
    所以会再从DB中查询。由于二级缓存中不存在该数据,所以命中率为0.但第二次查询是从二级缓存中读取的,所以这一次的命中率为1/2=0.5。
    当然,若有第三次查询,则命中率为1/3=0.66
    0.5这个值可以从上面开启cache的图看出来,0.0的值未截取到~漏掉了~

    注意:
    增删改操作,无论是否进行提交sqlSession.commit(),均会清空一级、二级缓存,使查询再次从DB中select。
    说明:
    二级缓存的清空,实质上是对所查找key对应的value置为null,而非将对,即entry对象删除。
    从DB中进行select查询的条件是:缓存中根本不存在这个key或者缓存中存在该key所对应的entry对象,但value为null。

    设置增删改操作不刷新二级缓存:
    若要使某个增、删或改操作不清空二级缓存,则需要在其或或中添加属性flushCache="false",默认为true。

    二级缓存的使用原则

    1. 只能在一个命名空间下使用二级缓存
      由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。在多个namespace中若均存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一致现象。
    2. 在单表上使用二级缓存
      如果一个表与其它表有关联关系,那么久非常有可能存在多个namespace对同一数据的操作。而不同namespace中的数据互补干扰,所以就有可能出现多个namespace中的数据不一致现象。
    3. 查询多于修改时使用二级缓存
      在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
    展开全文
  • hibernate二级缓存级缓存与二级缓存 1.hibernate级缓存 hibernate的级缓存是session级别的缓存,级缓存hibernate默认启用且不能被卸载,个事务内有效。 特点: 使用级缓存的目的是为了...

    hibernate二级缓存(一)一级缓存与二级缓存

    1.hibernate一级缓存

    hibernate的一级缓存是session级别的缓存,一级缓存hibernate默认启用且不能被卸载,一个事务内有效。
    特点:

    1. 使用一级缓存的目的是为了减少对数据库的访问次数,从而提升hibernate的执行效率;(当执行一次查询操作的时候,执行第二次查询操作,先检查缓存中是否有数据,如果有数据就不查询数据库,直接从缓存中获取数据);

    2. Hibernate中的一级缓存,也叫做session的缓存,它可以在session范围内减少数据库的访问次数,只在session范围内有效,session关闭,一级缓存失败;

    3. 一级缓存的特点,只在session范围有效,作用时间短,效果不是特别明显,在短时间内多次操作数据库,效果比较明显。

    4. 当调用session的save/saveOrUpdate/get/load/list/iterator方法的时候,都会把对象放入session缓存中;

    5. session的缓存是由hibernate维护的,用户不能操作缓存内容;如果想操作缓存内容,必须通过hibernate提供的evict/clear方法操作

    6. 缓存相关的方法(在什么情况下使用上面方法呢?批量操作情况下使用,如Session.flush();先与数据库同步,Session.clear();再清空一级缓存内容):

        session.flush();让一级缓存与数据库同步;
        session.evict();清空一级缓存中指定的对象;
        session.clear();清空一级缓存中所有的对象;
      

    综上: 一级缓存的生命周期和session的生命周期一致,当前session一旦关闭,一级缓存就消失了,因此一级缓存也叫session级的缓存或事务级缓存,一级缓存只存实体对象,它不会缓存一般的对象属性(查询缓存可以),即当获得对象后,就将该对象缓存起来,如果在同一session中再去获取这个对象时,它会先判断在缓存中有没有该对象的id,如果有则直接从缓存中获取此对象,反之才去数据库中取,取的同时再将此对象作为一级缓存处理。

    2.二级缓存

    Hibernate的二级缓存又称为"SessionFactory的缓存",由于SessionFactory对象的生命周期和应用的整个过程对应,他是可选的,是一个可配置的插件,默认情况下SessionFactory不会启用这个插件。
    由于二级缓存是被各session共享的,那么多个事务或者说线程同时访问修改二级缓存可能会会造成数据不一致问题。所以二级缓存只适合多读少写的场景。

    那么什么样的数据适合放在二级缓存中呢?

    • 多读少写的数据
    • 不是很重要的数据
    • 常量数据

    什么样的数据不适合放在二级缓存中呢?

    • 经常被修改的数据
    • 绝对不允许出现并发访问的数据。如财务数据,绝对不允许出现并发
    • 与其他应用共享的数据

    3. 二级缓存的配置

    这里只展示纯hibernate的二级缓存配置,如果要如spring结合,请参考spring sessionFactory配置里面的hibernate二级缓存参数。
    下面的配置是基于hibernate5.3.7.Final版本

    3.1 SessionFactory的配置

    该版本的SessionFactory获取的最新方式如下:

    public class SessionFactoryUtil {
    
        private SessionFactory sessionFactory;
    
        public SessionFactoryUtil() {
            setUp();
        }
    
        private void setUp() {
            // A SessionFactory is set up once for an application!
            final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                    .configure() // configures settings from hibernate.cfg.xml
                    .build();
            try {
                sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
            } catch (Exception e) {
                e.printStackTrace();
                // The registry would be destroyed by the SessionFactory, but we had trouble building the SessionFactory
                // so destroy it manually.
                StandardServiceRegistryBuilder.destroy(registry);
            }
        }
        public SessionFactory getSessionFactory() {
            return sessionFactory;
        }
    }
    

    registry 会自动加载resources路径下的hibernate.cfg.xml配置文件。hibernate.cfg.xml配置如下:

    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory>
            <!-- 数据库连接配置 -->
            <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
            <property name="connection.url">jdbc:mysql://localhost:3306/test</property>
            <property name="connection.username">root</property>
            <property name="connection.password">123456</property>
            <!-- 数据库连接池的大小 -->
            <property name="connection.pool_size">5</property>
            <!-- 每次从数据库中取出并放到JDBC的Statement中的记录条数。Fetch Size设的越大,读数据库的次数越少,速度越快,Fetch Size越小,读数据库的次数越多,速度越慢-->
            <property name="jdbc.fetch_size">50</property>
            <!--批量插入,删除和更新时每次操作的记录数。Batch Size越大,批量操作的向数据库发送Sql的次数越少,速度就越快,同样耗用内存就越大-->
            <property name="jdbc.batch_size">23</property>
            <!-- SQL 方言 -->
            <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
            <!-- Enable Hibernate's automatic session context management -->
            <property name="current_session_context_class">thread</property>
            <!-- 在控制台输出sql语句 -->
            <property name="show_sql">true</property>
            <!-- 在启动时根据配置更新数据库 -->
            <property name="hbm2ddl.auto">update</property>
    
            <!-- 二级缓存配置 -->
            <property name="hibernate.cache.use_second_level_cache">true</property>
            <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>
            <property name="hibernate.cache.use_minimal_puts">true</property>
            <property name="hibernate.cache.use_query_cache">true</property>
            <property name="hibernate.cache.region_prefix">customer-hibernate-cache</property>
            <property name="hibernate.cache.default_cache_concurrency_strategy">nonstrict-read-write</property>
    
    
            <mapping class="com.foo.model.Event"/><!-- 注册我们的实体映射类-->
        </session-factory>
    </hibernate-configuration>
    

    3.2 基本的代码示例

    有了SessionFactory后通过session进行增删改查等操作

    public class Main {
    
        public static void main(String[] args) {
            SessionFactoryUtil sessionFactoryUtil = new SessionFactoryUtil();
            SessionFactory sessionFactory = sessionFactoryUtil.getSessionFactory();
            //1.查询单个实体
            doExecute(sessionFactory, new HibernateExecuteCallBack() {
                @Override
                public void execute(Session session) {
                    Event event = session.get(Event.class, 7L);
                    System.out.println(event);
                }
            });
             //2.查询单个实体
            doExecute(sessionFactory, new HibernateExecuteCallBack() {
                @Override
                public void execute(Session session) {
                    Event event = session.get(Event.class, 7L);
                    System.out.println(event);
                }
            });
        }
    	public static void doExecute(SessionFactory sessionFactory, HibernateExecuteCallBack executeCallBack) {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
    
            executeCallBack.execute(session);
    
            session.getTransaction().commit();
            session.close();
        }
    
        interface HibernateExecuteCallBack {
            void execute(Session session);
        }
    
    

    上面的代码中首先得到一个sessionFactory ,然后通过doExecute封装session的整个执行流程的模版代码,HibernateExecuteCallBack 回调接口将实际执行操作分离,整个过程类似jdbcTemplate。

    package com.foo.model;
    
    import org.hibernate.annotations.CacheConcurrencyStrategy;
    
    import javax.persistence.*;
    import java.util.Date;
    
    /**
     * @author JasonLin
     * @version V1.0
     * @date 2019/3/11
     */
    @Entity
    @Table(name = "event")
    @Cacheable
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    public class Event {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column
        private String title;
    
        @Column(name = "_date")
        private Date date;
    
        public Event(){
    
        }
    
        public Event(String title, Date date) {
            this.title = title;
            this.date = date;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public Date getDate() {
            return date;
        }
    
        public void setDate(Date date) {
            this.date = date;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        @Override
        public String toString() {
            return "Event{" +
                    "id=" + id +
                    ", title='" + title + '\'' +
                    ", date=" + date +
                    '}';
        }
    }
    
    

    Event的代码随意,是通过注解配置实体,但是必须注意在hibernate.cfg.xml里面必须配置:

    <mapping class="com.foo.model.Event"/><!-- 注册我们的实体映射类-->
    

    3.2 二级缓存的配置

    在上面的配置里面其实已经加上了二级缓存

    		<!--是否启用二级缓存-->
            <property name="hibernate.cache.use_second_level_cache">true</property>
            <!--缓存的具体实现-->
            <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>
            <!--以频繁的读操作为代价, 优化二级缓存来最小化写操作-->
            <property name="hibernate.cache.use_minimal_puts">true</property>
            <!--是否使用查询缓存-->
            <property name="hibernate.cache.use_query_cache">true</property>
            <!--缓存的前缀-->
            <property name="hibernate.cache.region_prefix">customer-hibernate-cache</property>
            <!--缓存的策略-->
            <property name="hibernate.cache.default_cache_concurrency_strategy">nonstrict-read-write</property>
    

    这里我们使用的是EhcacheRegionFactory来作为二级缓存的具体实现。当然也可以自己实现RegionFactory,比如通过redis来作为hibernate的二级缓存。

    hibernate.cache.default_cache_concurrency_strategy指定hibernate二级缓存策略,hibernate共有五种缓存策略

    public enum CacheConcurrencyStrategy {
    	/**
    	 * Indicates no concurrency strategy should be applied.
    	 */
    	NONE( null ),
    	/**
    	 * Indicates that read-only strategy should be applied.
    	 *
    	 * @see AccessType#READ_ONLY
    	 */
    	READ_ONLY( AccessType.READ_ONLY ),
    	/**
    	 * Indicates that the non-strict read-write strategy should be applied.
    	 *
    	 * @see AccessType#NONSTRICT_READ_WRITE
    	 */
    	NONSTRICT_READ_WRITE( AccessType.NONSTRICT_READ_WRITE ),
    	/**
    	 * Indicates that the read-write strategy should be applied.
    	 *
    	 * @see AccessType#READ_WRITE
    	 */
    	READ_WRITE( AccessType.READ_WRITE ),
    	/**
    	 * Indicates that the transaction strategy should be applied.
    	 *
    	 * @see AccessType#TRANSACTIONAL
    	 */
    	TRANSACTIONAL( AccessType.TRANSACTIONAL );
    
    • CacheConcurrencyStrategy.None 不使用缓存
    • CacheConcurrencyStrategy.READ_ONLY:只读模式,在此模式下,如果对数据进行更新操作,会抛出异常。
    • CacheConcurrencyStrategy.READ_WRITE:读写模式在更新缓存的时候会对缓存里的数据加锁,其他事物如果去取相应缓存中的数据,发现被锁了,直接去数据库中取。
    • CacheConcurrencyStrategy.NONSTRICT_READ_WRITE:不严格的读写模式则不会对缓存数据加锁
    • CacheConcurrencyStrategy.TRANSACTIONAL:事务模式指缓存支持事务,当事务回滚,缓存也回滚,只支持JTA环境

    一切配置完毕,下面来看下具体的执行结果,

    Hibernate: select event0_.id as id1_0_0_, event0_._date as _date2_0_0_, event0_.title as title3_0_0_ from event event0_ where event0_.id=?
    Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
    Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
    

    由于我们的第一次操作是在不同的session里面,我们看到配置了缓存之后只发送了一条sql语句。代表缓存配置成功。在后面我们将具体讲解hibernate二级缓存的实现原理并自己用map实现一个简单的RegionFactory。

    展开全文
  • 今天小编就为大家分享篇关于Hibernate级缓存和二级缓存详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • Mybatis级缓存与二级缓存

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

    mybatis的有两种缓存,一级缓存和二级缓存。两个缓存的不同点和相同点总结如下

    不同点:

    • 一级缓存存在于一个SqlSession之内,二级缓存存在于不同的SqlSession之间
    • 一级缓存不需要手动开启,属于默认开启状态;二级缓存需要手动开启

    相同点:

    • 在增删改SQL之后,缓存会自动清空
    • flushCache="true"的查询语句查询内容不存放进缓存

     

    一级缓存

    一级缓存是mybatis自带的缓存,mybatis每次在查询后,会将语句和参数相同的查询SQL的结果集存放进缓存,待下一次有相同的语句和参数时,mybatis直接将缓存内的结果集返回,而不再查询数据库。如果对于缓存的数据对应的表有增删改操作的话,缓存自动清空。

    通过实际的代码测试来看,在上一次一个简单的mybatis入门demo的基础上改造工程

    增加BaseMaperTest.java类,用以进行SqlSession的获取

    package cn.mybatis.xml;
    
    import java.io.IOException;
    import java.io.Reader;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.BeforeClass;
    
    /**
     * 设置mapper测试父类
     * 用以进行数据库连接,获取SqlSession
     * @author PC
     */
    public class BaseMapperTest {
    
    	private static SqlSessionFactory sqlSessionFactory;
    	
    	/**
    	 * 进行数据库连接
    	 */
    	@BeforeClass
    	public static void init() {
    		try {
    			Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    			sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    			reader.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	/**
    	 * 获取SqlSession
    	 * @return
    	 */
    	public SqlSession getSqlSession() {
    		return sqlSessionFactory.openSession();
    	}
    }
    

    在CountryMapper.java中增加根据id查询方法,增加一个addCountry方法

    	/**
    	 * 查询国家/地区
    	 * @param id 查询id
    	 * @return 查询到的国家/地区
    	 */
    	Country selectCountryById(Long id);
    	
    	/**
    	 * 添加国家/地区
    	 * @param country
    	 * @return 影响的数据条数
    	 */
    	int addCountry(Country country);

    对应CountryMapper.xml配置文件

    	<select id="selectCountryById" resultType="cn.mybatis.xml.model.Country">
    		select id, countryname, countrycode 
    		from country
    		where id = #{id}
    	</select>
    	
    	<insert id="addCountry">
    		insert into country(id, countryname, countrycode)
    		values(#{id}, #{countryname}, #{countrycode})
    	</insert>

    通过Junit来测试,三种场景,分别来测试

    • 缓存后,直接查询
    	/**
    	 * 一级缓存测试
    	 * 测试缓存后,再查询
    	 */
    	@Test
    	public void testCache1() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			// 第一次查询
    			Country country = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country.getCountryname() + ":" + country.getCountrycode());
    			
    			// 通过日志可以发现,第二次查询并未到数据库查数据,说明第二次走的是缓存
    			Country country2 = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country2.getCountryname() + ":" + country2.getCountrycode());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后,可以看到日志

    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    美国:US
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.

    反馈了两次查询结果,但是只查询了一次数据库,说明第二次的查询是取得缓存结果

    • 缓存后,增删改,再查询
    	/**
    	 * 一级缓存测试
    	 * 测试缓存后,增删改查,再查询
    	 */
    	@Test
    	public void testCache2() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			// 第一次查询
    			Country country = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country.getCountryname() + ":" + country.getCountrycode());
    			
    			Country country2 = new Country();
    			country2.setId(7);
    			country2.setCountrycode("TW");
    			country2.setCountryname("中国台湾");
    			int result = sqlSession.insert("addCountry", country2);
    			if (result == 1) {
    				System.out.println("** insert success **");
    			}
    			
    			// 由于进行了insert操作,第二次查询没有走缓存,直接走的数据库查询
    			Country country3 = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country3.getCountryname() + ":" + country3.getCountrycode());
    		} finally {
    			sqlSession.commit();
    			sqlSession.close();
    		}
    	}

    执行结果

    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    ==>  Preparing: insert into country(id, countryname, countrycode) values(?, ?, ?) 
    ==> Parameters: 7(Integer), 中国台湾(String), TW(String)
    <==    Updates: 1
    ** insert success **
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.

    通过日志,可以印证,在缓存后,如果执行增删改操作,之前缓存的数据会自动清空。

    • 不缓存时的两连查

    此时,我们需要在查询语句的标签中增加 flushCache="true" ,意为查询结果不缓存。

    增加后的SQL标签为

    	<select id="selectCountryById" flushCache="true" resultType="cn.mybatis.xml.model.Country">
    		select id, countryname, countrycode 
    		from country
    		where id = #{id}
    	</select>

    测试代码

    	/**
    	 * 一级缓存测试
    	 * 测试select查询,不存入缓存,再查询
    	 */
    	@Test
    	public void testCache3() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			// 第一次查询,但是SQL设置了flushCache="true",即查询结果不会缓存
    			Country country = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country.getCountryname() + ":" + country.getCountrycode());
    			
    			// 通过日志可以发现,第二次查询依然查询了数据库,查询出来的结果依然不会缓存
    			Country country2 = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country2.getCountryname() + ":" + country2.getCountrycode());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后的日志

    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    

    可以看出,两次查询都走了数据库查询,原因就在于flushCache="true" 上面。flushCache标签,意为查询结果后是否缓存,默认为false,即默认存入缓存,方便后续查询。如果不想存缓存,则需要手动的设置为true,查询结果不会存缓存。一般不推荐这么做,这么做会增加数据库的负担,增加一些不必要的查询。

     

    通过上面的测试,一级缓存的场景可以总结如下:

    直接查,存缓存;增删改,清缓存;flushCache不缓存。

    这里的flushCache值得是手动设置flushCache为true的情形。

     

    二级缓存

    相较于一级缓存的自动默认开启,二级缓存需要手动开启。一级缓存在同一个SqlSession内,以SqlSession为缓存单位;二级缓存在不同的SqlSession间,以mapper为单位,即不同的SqlSession间可以共享相同的mapper下接口查询的数据。

    准备测试环境

    增加SysUser.java

    package cn.mybatis.xml.model;
    
    import java.io.Serializable;
    import java.util.Date;
    
    /**
     * 用户表
     * @author PC
     */
    public class SysUser implements Serializable{
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 1L;
    
    	/**
    	 * 用户ID
    	 */
    	private Long id;
    	
    	/**
    	 * 用户名
    	 */
    	private String userName;
    	
    	/**
    	 * 密码
    	 */
    	private String userPassword;
    	
    	/**
    	 * 邮箱
    	 */
    	private String userEmail;
    	
    	/**
    	 * 简介
    	 */
    	private String userInfo;
    	
    	/**
    	 * 头像
    	 */
    	private byte[] headImg;
    	
    	/**
    	 * 创建时间
    	 */
    	private Date createTime;
    
    	getter and setter...
    }
    

    mybatis-config.xml中增加UserMapper.xml配置

    	<mappers>
    		<mapper resource="cn/mybatis/xml/mapper/CountryMapper.xml" />
    		<mapper resource="cn/mybatis/xml/mapper/UserMapper.xml" />
    	</mappers>

    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">
    <!-- 定义当前XML的命名空间 -->
    <mapper namespace="cn.mybatis.xml.mapper.UserMapper">
    
    	<!-- 启用mybatis二级缓存,不设置具体数值,均为默认项 -->
    	<cache/>
    	
    	<!-- resultMap 设置返回值的类型和映射关系 -->
    	<resultMap id="userMap" type="cn.mybatis.xml.model.SysUser">
    		<id property="id" column="id"/>
    		<result property="userName" column="user_name"/>
    		<result property="userPassword" column="user_password"/>
    		<result property="userEmail" column="user_email"/>
    		<result property="userInfo" column="user_info"/>
    		<result property="headImg" column="head_img" jdbcType="BLOB"/>
    		<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    	</resultMap>
    	
    	<!-- ID属性,定义当前select查询的唯一ID;resultType,定义当前查询的返回值类型,上面设置了resultMap的别名,这里引用id即可 -->
    	<select id="selectUserByUserId" resultMap="userMap">
    		select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = #{id}
    	</select>
    	
    	<!-- 增加user -->
    	<insert id="addUser">
    		insert into sys_user(id, user_name,user_password,user_email,user_info) values(#{id}, #{userName}, #{userPassword}, #{userEmail}, #{userInfo});
    	</insert>
    	
    	<delete id="deleteUser">
    		delete from sys_user where id = #{id}
    	</delete>
    </mapper>

    上面,看到了<cache/>,这个标签标示启用二级缓存,二级缓存有一系列的参数策略,这里不配置任何内容,表示均使用默认值。

    通过Junit来测试,主要测试三种不同的场景

    • 不同SqlSession之间的查询
    	/**
    	 * 二级缓存测试
    	 * 不同SqlSession之间的查询
    	 */
    	@Test
    	public void testCache1() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			SysUser user = sqlSession.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    		
    		// 二级缓存,在第一个session关闭时,数据存入二级缓存中
    		SqlSession sqlSession2 = getSqlSession();
    		try {
    			SysUser user = sqlSession2.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后,通过日志可看到

    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.5
    test
    

    执行了一次数据库查询,第二次查询时直接通过获取缓存的值返回,证明二级缓存生效。

    • 缓存后,执行增删改,再查询
    	/**
    	 * 二级缓存测试
    	 * 不同SqlSession之间查询,增删改,再查询
    	 */
    	@Test
    	public void testCache2() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			SysUser user = sqlSession.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    		
    		SqlSession sqlSession2 = getSqlSession();
    		try {
    			int result = sqlSession2.delete("deleteUser", 2001l);
    			System.out.println(result);
    		} finally {
    			sqlSession2.commit();
    			sqlSession2.close();
    		}
    		
    		// 第二次查询
    		SqlSession sqlSession3 = getSqlSession();
    		try {
    			SysUser user = sqlSession3.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后,看日志

    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    Opening JDBC Connection
    Checked out connection 1291113768 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: delete from sys_user where id = ? 
    ==> Parameters: 2001(Long)
    <==    Updates: 1
    1
    Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
    Opening JDBC Connection
    Checked out connection 1291113768 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    

    可以发现,在执行了delete语句后,缓存被清空了,待第二次查询时,又查了数据库。

    其实上面在说一级缓存时有说到,任何的增删改的语句,都会清空一级缓存,二级缓存自然会被清空了。

    • 关闭二级缓存总开关

    上面的两个场景测试,都是在mapper文件中设置使用二级缓存,二级缓存其实还有一个总开关,在mybatis-config.xml文件的setting配置中,为何之前并没有去配这个开关呢,这个开关默认打开的,只需要在mapper.xml文件中配置二级缓存开关即可。

    	<settings>
    		<!-- mybatis 二级缓存总开关,总开关默认为true -->
    		<!-- 总开关打开后,然后在每个mapper中设置自己的二级缓存开关;若总开关关闭,则后续mapper设置均无效 -->
    		<setting name="cacheEnabled" value="false"/>
    		<!-- 设置驼峰匹配 -->
    		<setting name="mapUnderscoreToCamelCase" value="true" />
    		<!-- 配置指定使用log4j输出日志 -->
    		<setting name="logImpl" value="STDOUT_LOGGING" />
    	</settings>

    该标签为cacheEnabled,默认值为true,所以,默认的二级缓存的总开关是打开的,不需要手动设置。

    我们现在将其设置为false,再次执行二级缓存场景一的测试语句

    可以看到日志

    Opening JDBC Connection
    Created connection 345281752.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    Returned connection 345281752 to pool.
    Opening JDBC Connection
    Checked out connection 345281752 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test

    两次查询,均走了数据库查询。

     

    综上:二级缓存可以作为一级缓存的补充,一级缓存在同一个SqlSession之间,二级缓存将缓存扩大到不同的SqlSession之间。相同的点是,一旦有增删改的操作,缓存均会清空。

     

    以上是本人学习mybatis缓存的简单认知,记录下来,供初学的童鞋予以参考。如果内容有错误或疏漏部分,还望批评指正。谢谢。

     

    附 工程源代码路径:mybatis一级缓存和二级缓存简单示例

    展开全文
  • 主要介绍了mybatis教程之查询缓存(级缓存二级缓存和整合ehcache),具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • mybatis的缓存机制(级缓存二级缓存和刷新缓存)和mybatis整合ehcache

     

     1      查询缓存

    1.1  什么是查询缓存
    mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
    mybaits提供一级缓存,和二级缓存。

    一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。


    一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。


    二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。


    二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。

    如果缓存中有数据就不用从数据库中获取,大大提高系统性能。


    1.2  一级缓存
    1.2.1    一级缓存工作原理
    下图是根据id查询用户的一级缓存图解
    第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
    得到用户信息,将用户信息存储到一级缓存中。
    如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

    第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。


    1.2.2    一级缓存测试


    mybatis默认支持一级缓存,不需要在配置文件去配置。
    按照上边一级缓存原理步骤去测试。

    @Test
    public void testCache1() throws Exception{
    SqlSessionsqlSession = sqlSessionFactory.openSession();//创建代理对象
    UserMapperuserMapper = sqlSession.getMapper(UserMapper.class);
    //下边查询使用一个SqlSession
    //第一次发起请求,查询id为1的用户
    Useruser1 = userMapper.findUserById(1);
    System.out.println(user1);
    //    如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
    //更新user1的信息
    user1.setUsername('测试用户22');
    userMapper.updateUser(user1);
    //执行commit操作去清空缓存
    sqlSession.commit();
    //第二次发起请求,查询id为1的用户
    Useruser2 = userMapper.findUserById(1);
    System.out.println(user2);
    sqlSession.close();
    }



    1.2.3    一级缓存应用


    正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
    一个service方法中包括很多mapper方法调用。
    service{
    //开始执行时,开启事务,创建SqlSession对象
    //第一次调用mapper的方法findUserById(1)
    //第二次调用mapper的方法findUserById(1),从一级缓存中取数据
    //aop控制 只要方法结束,sqlSession关闭 sqlsession关闭后就销毁数据结构,清空缓存
    Service结束sqlsession关闭
    }

    如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为Service方法结束,sqlSession就关闭,一级缓存就清空。


    1.3  二级缓存
    1.3.1    原理
    首先开启mybatis的二级缓存。
    sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
    如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据
    sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
    二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。数据类型仍然为HashMap
    UserMapper有一个二级缓存区域(按namespace分,如果namespace相同则使用同一个相同的二级缓存区),其它mapper也有自己的二级缓存区域(按namespace分)。

    每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。



    1.3.2    开启二级缓存


    mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
    在核心配置文件SqlMapConfig.xml中加入

    <settingname='cacheEnabled'value='true'/>
    <!-- 全局配置参数,需要时再设置 -->
    <settings>
    <!-- 开启二级缓存 默认值为true -->
    <settingname='cacheEnabled'value='true'/>
    </settings>


      描述                                                                                                                   允许值          默认值
    cacheEnabled   对在此配置文件下的所有cache 进行全局性开/关设置。            true false       true

    在UserMapper.xml中开启二缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)。
    <mappernamespace='cn.hpu.mybatis.mapper.UserMapper'>
    <!-- 开启本mappernamespace下的二级缓存 -->
    <cache></cache>


    1.3.3    调用pojo类实现序列化接口


    public class User implements Serializable {
    //Serializable实现序列化,为了将来反序列化

    二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。

    pojo类实现序列化接口是为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存有可能是硬盘或者远程服务器。


    1.3.4    测试方法
    // 二级缓存测试
    @Test
    public void testCache2() throws Exception {
    SqlSessionsqlSession1 = sqlSessionFactory.openSession();
    SqlSessionsqlSession2 = sqlSessionFactory.openSession();
    SqlSessionsqlSession3 = sqlSessionFactory.openSession();
    // 创建代理对象
    UserMapperuserMapper1 = sqlSession1.getMapper(UserMapper.class);
    // 第一次发起请求,查询id为1的用户
    Useruser1 = userMapper1.findUserById(1);
    System.out.println(user1);
    //这里执行关闭操作,将sqlsession中的数据写到二级缓存区域
    sqlSession1.close();
    //使用sqlSession3执行commit()操作
    UserMapperuserMapper3 = sqlSession3.getMapper(UserMapper.class);
    Useruser  = userMapper3.findUserById(1);
    user.setUsername('张明明');
    userMapper3.updateUser(user);
    //执行提交,清空UserMapper下边的二级缓存
    sqlSession3.commit();
    sqlSession3.close();
    UserMapperuserMapper2 = sqlSession2.getMapper(UserMapper.class);
    // 第二次发起请求,查询id为1的用户
    Useruser2 = userMapper2.findUserById(1);
    System.out.println(user2);
    sqlSession2.close();
    }


    1.3.5  useCache配置禁用二级缓存


    在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
    <selectid='findOrderListResultMap' resultMap='ordersUserMap' useCache='false'>

    总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。


    1.3.6    mybatis刷新缓存(就是清空缓存)


    在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
    设置statement配置中的flushCache='true' 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
    如下:
    <insertid='insertUser' parameterType='cn.itcast.mybatis.po.User' flushCache='true'>

    总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存默认情况下为true,我们不用去设置它,这样可以避免数据库脏读。


    1.3.7  Mybatis Cache参数


    flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
    size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
    readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
    如下例子:
    <cache  eviction='FIFO' flushInterval='60000'  size='512' readOnly='true'/>

    这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:
    1.      LRU – 最近最少使用的:移除最长时间不被使用的对象。
    2.      FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    3.      SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

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


    1.4  mybatis整合ehcache
    ehcache是一个分布式缓存框架。

    EhCache 是一个纯Java的进程内缓存框架,是一种广泛使用的开源Java分布式缓存,具有快速、精干等特点,是Hibernate中默认的CacheProvider。


    1.4.1    分布缓存
    我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)
    不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理。

    mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。


    1.4.2    整合方法(掌握无论整合谁,首先想到改type接口)


    mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。

    mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类。


    1.4.3    第一步加入ehcache包


    1.4.4    整合ehcache

    配置mapper中cache中的type为ehcache对cache接口的实现类型。
    <mappernamespace='cn.hpu.mybatis.mapper.UserMapper'>
    <!-- 开启本mappernamespace下的二级缓存
    type:指定cache接口实现类,mybatis默认使用PerpetualCache
    要和eache整合,需要配置type为ehcahe实现cache接口的类型
    -->
    <cachetype='org.mybatis.caches.ehcache.EhcacheCache'>
    </cache>

    可以根据需求调整缓存参数:
    <cachetype='org.mybatis.caches.ehcache.EhcacheCache'>
    <propertyname='timeToIdleSeconds'value='3600'/>
    <propertyname='timeToLiveSeconds'value='3600'/>
    <!-- 同ehcache参数maxElementsInMemory-->
    <propertyname='maxEntriesLocalHeap'value='1000'/>
    <!-- 同ehcache参数maxElementsOnDisk -->
    <propertyname='maxEntriesLocalDisk'value='10000000'/>
    <propertyname='memoryStoreEvictionPolicy'value='LRU'/>
    </cache>


    1.4.5    加入ehcache的配置文件
    在classpath下配置ehcache.xml
    <ehcachexmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xsi:noNamespaceSchemaLocation='../config/ehcache.xsd'>
    <diskStorepath='F:\develop\ehcache'/>
    <defaultCache
        maxElementsInMemory='1000'
        maxElementsOnDisk='10000000'
        eternal='false'
        overflowToDisk='false'
        timeToIdleSeconds='120'
        timeToLiveSeconds='120'
        diskExpiryThreadIntervalSeconds='120'
        memoryStoreEvictionPolicy='LRU'>
    </defaultCache>
    </ehcache>

    属性说明:
     diskStore:指定数据在磁盘中的存储位置。
    defaultCache:当借助CacheManager.add('demoCache')创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
    以下属性是必须的:
    maxElementsInMemory - 在内存中缓存的element的最大数目
    maxElementsOnDisk- 在磁盘上缓存的element的最大数目,若是0表示无穷大
    eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断

    overflowToDisk- 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上


    以下属性是可选的:
    timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
    timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
    diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
    diskPersistent在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
    diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作

    memoryStoreEvictionPolicy- 当内存缓存达到最大,有新的element加入的时候,移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)


    1.5  二级应用场景
    对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。

    实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。


    1.6  二级缓存局限性

    mybatis二级缓存对细粒度的数据级别的缓存实现不好,对同时缓存较多条数据的缓存,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。需要使用三级缓存



    本文转自于:http://www.360doc7.net/wxarticlenew/518018352.html



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

    千次阅读 多人点赞 2020-04-22 10:18:33
    Mybatis中的级缓存和二级缓存: 【级缓存】 它指的是Mybatis中SqlSession对象的缓存 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供块区域中。该区域的结构是个Map。 当我们再次查询...
  • 配套博客内容,mybatis级缓存和二级缓存简单示例,供初学童鞋予以参考。 博客地址 https://blog.csdn.net/magi1201/article/details/85524712
  • 文章目录、前言二、级缓存三、二级缓存3.1、开启二级缓存:四、测试一下五、总结: 、前言 先说缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,...
  • 1.一级缓存属于 局部缓存 (操作时命中率低) 存放数据的形式:相互关联的持久化对象 缓存的范围:事务范围,每个事务都拥有单独的一级缓存 并发访问策略:由于每个事务都拥有单独的一级缓存不会出现并发...
  • 1、缓存 (1)概念:在内存中开辟的个区域,用于存放数据,在内存中存放的数据叫做缓存。 (2)好处:内存读取速度远快于硬盘,合理利用缓存,可以极大的提高查询的效率。...1.2 二级缓存 1、概念 (1)什...
  • 二级缓存作用域是针对mapper进行缓存. 一级缓存: 1、第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓...
  • IUserDao.java,User.java代码与之前类似,就不写了 ...4.Mybatis中的级缓存和二级缓存 级缓存: 它指的是Mybatis中SqlSession对象的缓存。 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供
  • Hibernate级缓存、二级缓存以及查询缓存实例
  • 级缓存和二级缓存区别

    千次阅读 2016-08-16 21:06:14
    级缓存和二级缓存区别 级缓存:  就是Session级别的缓存。个Session做了个查询操作,它会把这个操作的结果放在级缓存中。  如果短时间内这个session(一定要同个session)又做了同个操作,那么...
  • 前两天总结了一下二级缓存和查询缓存的关系,但是又有个新的问题,就是查询缓存缓存到二级缓存的数据,在第三次(第次缓存中没有数据,查询数据库将对应的ID值存入到二级缓存中去,第二次如果是同个Session...
  • 1、先判断二级缓存是否开启,如果没开启,再判断级缓存是否开启,如果没开启,直接查数据库 2、如果级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从级缓存获取 3、一般不会关闭级缓存 4、...
  • 一对一映射 hibernate优化配置一级二级缓存步骤 有图有真相
  • Mybatis级缓存和二级缓存区别

    万次阅读 多人点赞 2020-03-11 21:35:06
    1)一级缓存 Mybatis的一级缓存是指SQLSession,一级缓存的作用域是SQlSession, Mabits默认开启一级缓存。 在同个SqlSession中,执行相同的SQL查询时;第次会去查询数据库,并写在缓存中,第次会直接从缓存中...
  • CPU中的级缓存,二级缓存,三级缓存

    千次阅读 多人点赞 2018-06-14 13:49:56
    CPU中的级缓存,二级缓存,三级缓存 缓存又叫高速缓冲存储器,其作用在于缓解主存速度慢、跟不上CPU读写速度要求的矛盾。 缓存的实现原理,是把CPU最近最可能用到的少量信息(数据或指令)从主存复制到CACHE中,当...
  • Mybatis中有级缓存和二级缓存,默认情况下级缓存是开启的,而且是不能关闭的。级缓存是指SqlSession级别的缓存,当在同个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接...
  • hibernate的级缓存和二级缓存,hibernate的级缓存和二级缓存,hibernate的级缓存和二级缓存,hibernate的级缓存和二级缓存,hibernate的级缓存和二级缓存
  • Hibernate的Session提供了级缓存的功能,默认总是有效的,当应用程序保存持久化实体、修改持久化实体时,Session并不会立即把这种改变提交到数据库,而是缓存在当前的Session中,...SessionFactory级别的二级缓存...
  • 主要给大家深入的介绍了关于MyBatis中级缓存与二级缓存的相关资料,文中详细介绍MyBatis中级缓存与二级缓存的工作原理及使用,对大家具有一定的参考性学习价值,需要的朋友们下面来一起看看吧。
  • 一级缓存:  也称本地缓存,sqlSession级别的缓存。一级缓存是一直开启的;与数据库同一次会话期间查询到的数据会放在本地缓存中。  如果需要获取相同的数据,直接从缓存中拿,不会再查数据库。  一级缓存失效...
  • • 一级 缓存和 二级缓存。 – 1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。 – 2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。 – 3、为了提高扩展性。MyBati....
  • Mybatis的级缓存和二级缓存详解

    千次阅读 多人点赞 2018-11-19 19:11:59
    注:本笔记是根据尚硅谷的MyBatis视频记录的 对于任何个持久层框架,都有缓存机制;缓存在电脑中有块真实的存储空间...关于Mybatis的级缓存和二级缓存执行顺序具体可参考:Mybatis的级缓存和二级缓存执行...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 448,693
精华内容 179,477
关键字:

一级二级缓存区别