Mybaties 的缓存机制详解

一级缓存 MyBatis 包含了一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了, 使得它更加强大而且易于配置。 mybatis默认情况下只会开启一级缓存 ,也就是局部的 session 会话缓存。 功能:mybaties 提供查询缓存,用于减轻数据压力,提供数据库性能。 如图所示,每一个session 会话都会有各自的缓存,这缓存是局部的。 Mybaties 的缓存机制详解

一级缓存是SqlSession级别的缓存。我们都知道在操作数据库时需要构造 sqlSession对象,而在sqlSession对象中有一个数据结构(HashMap)用于存储缓存数据

org.apache.ibatis.session 包下有一个Configuration类配置缓存,可以看到localCacheScope的默认级别为Session,二级缓存默认开启

public Configuration() {
    this.safeResultHandlerEnabled = true;
    this.multipleResultSetsEnabled = true;
    this.useColumnLabel = true;
    this.cacheEnabled = true;  //二级缓存的开关,暂时不需要管
    this.useActualParamName = true;
    this.localCacheScope = LocalCacheScope.SESSION;  //一级缓存的范围
    this.jdbcTypeForNull = JdbcType.OTHER;
    this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
    this.defaultExecutorType = ExecutorType.SIMPLE;
    this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
    this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
    this.variables = new Properties();
    this.reflectorFactory = new DefaultReflectorFactory();
    this.objectFactory = new DefaultObjectFactory();
    this.objectWrapperFactory = new DefaultObjectWrapperFactory();
    this.lazyLoadingEnabled = false;
    this.proxyFactory = new JavassistProxyFactory();
    this.mapperRegistry = new MapperRegistry(this);
    this.interceptorChain = new InterceptorChain();
    this.typeHandlerRegistry = new TypeHandlerRegistry();
    this.typeAliasRegistry = new TypeAliasRegistry();
    this.languageRegistry = new LanguageDriverRegistry();
    this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
    this.caches = new Configuration.StrictMap("Caches collection");
    this.resultMaps = new Configuration.StrictMap("Result Maps collection");
    this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
    this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
    this.loadedResources = new HashSet();
    this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
    this.incompleteStatements = new LinkedList();
    this.incompleteCacheRefs = new LinkedList();
    this.incompleteResultMaps = new LinkedList();
    this.incompleteMethods = new LinkedList();
    this.cacheRefMap = new HashMap();
    this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
    this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    this.languageRegistry.register(RawLanguageDriver.class);
}

一级缓存测试

当mybaties  在会话中开启事务时,所有操作结束后才会提交,这时就不会在第一次查询操作的时候刷新一级缓存,

第二次查询就可以使用二级缓存。

Mybaties 的缓存机制详解

1.代码实例

@Transactional
@Override
public ContractBaseProduct selectContractBaseProductById(Long id) {

    ContractBaseProduct contractBaseProduct = contractBaseProductMapper.selectContractBaseProductById(id);
    System.out.println("product1: " + contractBaseProduct);

    ContractBaseProduct contractBaseProduct01 = contractBaseProductMapper.selectContractBaseProductById(id);
    System.out.println("product2: " + contractBaseProduct01);
return contractBaseProduct;
}

执行结果:

16:36:39.355 [http-nio-9999-exec-7] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
16:36:39.356 [http-nio-9999-exec-7] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
16:36:39.358 [http-nio-9999-exec-7] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product1: com.icfcc.project.system.product.domain.ContractBaseProduct@133d0247[id=7,name=1TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]
product2: com.icfcc.project.system.product.domain.ContractBaseProduct@133d0247[id=7,name=1TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]

当mybaties  在会话中不开启事务时,每次操作都会提交并刷新一级缓存,这时二级缓存就用不到了。

Mybaties 的缓存机制详解

代码示例:

@Override
public ContractBaseProduct selectContractBaseProductById(Long id) {

    ContractBaseProduct contractBaseProduct = contractBaseProductMapper.selectContractBaseProductById(id);
    System.out.println("product1: " + contractBaseProduct);

    ContractBaseProduct contractBaseProduct01 = contractBaseProductMapper.selectContractBaseProductById(id);
    System.out.println("product2: " + contractBaseProduct01);
return contractBaseProduct;
}

执行结果:

16:08:19.450 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
16:08:19.451 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
16:08:19.455 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product1: com.icfcc.project.system.product.domain.ContractBaseProduct@1284c25e[id=7,name=1TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]
16:08:19.458 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
16:08:19.459 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
16:08:19.462 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product2: com.icfcc.project.system.product.domain.ContractBaseProduct@6a1c82fc[id=7,name=1TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]

结论:在没有添加事务时,这时发现sql 打印了两次,说明没有使用一级缓存

源码分析:

Mybaties 的缓存机制详解

在执行commit()方法时, 会清空本地缓存 。那么以后再次查询时,缓存中总是找不到对应的key值,就会出现每次都重新执行sql语句,去数据库中查询的现象了 , 要想支持一级缓存,就得要保证在这些sql语句全部执行完以后,再去执行commit()方法,也就是说,我们的方法必须要在同一个事务内,才会支持一级缓存。 二级缓存 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是可以横跨跨SqlSession的。 示意图: Mybaties 的缓存机制详解

二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分,也就是根据命名空间来划分的,如果两个mapper文件的命名空间一样,那样,不同的SqlSession之间就可以共享一个mapper缓存。

示意图:

Mybaties 的缓存机制详解

开启二级缓存:

1)在mytatis 主配置文件里加入:

<settings>
    <!-- 对在此配置文件下的所有cache进行全局性的开/关设置。默认值:true -->
    <setting name="cacheEnabled" value="true"/>
</settings>

2)在需要被缓存的SQL映射文件中添加一行cache配置:

<mapper namespace="com.icfcc.project.system.product.mapper.ContractBaseProductMapper">
    <cache/>
</mapper>    

代码示例:

public ContractBaseProduct selectContractBaseProductById(Long id) {

        SqlSession sqlSession = sqlSessionFactory.openSession();
        ContractBaseProductMapper mapper = sqlSession.getMapper(ContractBaseProductMapper.class);
        ContractBaseProduct contractBaseProduct = mapper.selectContractBaseProductById(id);
        System.out.println("product1: " + contractBaseProduct);
        sqlSession.close();

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        ContractBaseProductMapper mapper1 = sqlSession1.getMapper(ContractBaseProductMapper.class);
        ContractBaseProduct contractBaseProduct01 = mapper1.selectContractBaseProductById(id);
        System.out.println("product2: " + contractBaseProduct01);
        sqlSession1.close();
        return contractBaseProduct;
    }

执行结果:

22:42:25.899 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.ContractBaseProductMapper - [getObject,62] - Cache Hit Ratio [com.icfcc.project.system.product.mapper.ContractBaseProductMapper]: 0.0
22:42:25.900 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
22:42:25.900 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
22:42:25.903 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product1: com.icfcc.project.system.product.domain.ContractBaseProduct@80fefbb[id=7,name=TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]
22:42:25.906 [http-nio-9999-exec-10] DEBUG c.i.p.s.p.m.ContractBaseProductMapper - [getObject,62] - Cache Hit Ratio [com.icfcc.project.system.product.mapper.ContractBaseProductMapper]: 0.25
product2: com.icfcc.project.system.product.domain.ContractBaseProduct@157929bd[id=7,name=TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIP]

结论:当开启二级缓存后,同一个 namespace 中的查询sql 只执行一次,第二次会从二级缓存中读取。

禁用查询语句的二级缓存: 在statement中设置useCache="false"就可以禁用当前select语句的二级缓存,也就是每次都会生成sql去查询默认情况下默认是true,也就是默认使用二级缓存。如下示例:
<select id="selectContractBaseProductById" parameterType="Long" resultMap="ContractBaseProductResult" useCache="false">
   <include refid="selectContractBaseProductVo"/>
   where id = #{id}
</select>

执行结果:查询了两次

20:33:47.295 [http-nio-9999-exec-26] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
20:33:47.296 [http-nio-9999-exec-26] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
20:33:47.300 [http-nio-9999-exec-26] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product1: com.icfcc.project.system.product.domain.ContractBaseProduct@37d3fe31[id=7,name=TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]
20:33:47.303 [http-nio-9999-exec-26] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
20:33:47.304 [http-nio-9999-exec-26] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
20:33:47.308 [http-nio-9999-exec-26] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product2: com.icfcc.project.system.product.domain.ContractBaseProduct@1b17f266[id=7,name=TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]
刷新查询语句的缓存: flushCache="true"属性,会默认刷新缓存, 任何时候只要语句被调用,都会导致一级缓存和二级缓存都会被清空,默认值:false 代码示例:
<select id="selectContractBaseProductById" parameterType="Long" resultMap="ContractBaseProductResult" flushCache="true">
    <include refid="selectContractBaseProductVo"/>
    where id = #{id}
</select>

执行结果:查询了两次

20:42:02.713 [http-nio-9999-exec-12] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
20:42:02.714 [http-nio-9999-exec-12] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
20:42:02.717 [http-nio-9999-exec-12] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product1: com.icfcc.project.system.product.domain.ContractBaseProduct@51a42596[id=7,name=TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]
20:42:02.720 [http-nio-9999-exec-12] DEBUG c.i.p.s.p.m.ContractBaseProductMapper - [getObject,62] - Cache Hit Ratio [com.icfcc.project.system.product.mapper.ContractBaseProductMapper]: 0.25
20:42:02.721 [http-nio-9999-exec-12] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==>  Preparing: select id, name, profile, type, head, mobile, email, version, publish_date, development_team, description from contract_base_product where id = ?
20:42:02.725 [http-nio-9999-exec-12] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - ==> Parameters: 7(Long)
20:42:02.728 [http-nio-9999-exec-12] DEBUG c.i.p.s.p.m.C.selectContractBaseProductById - [debug,159] - <==      Total: 1
product2: com.icfcc.project.system.product.domain.ContractBaseProduct@2027e457[id=7,name=TBPS开发,profile=TBPS是在完全按照中国人民银行公布的接口标准的要求,并结合银行核心系统的现状开发的一款产品,TBPS既满足人民银行的各项联网要求,又最大可能减轻银行后台的开发工作量,使得银行系统可以以最小的改动、最少的成本、最快的速度平稳接入TIPS]

上一篇:Spring整合myBaties -- xml形式


下一篇:云栖大会第二天:ACK Anywhere 来了