赞
踩
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存(本地缓存也就是一级缓存是sqlsession级别的),它仅仅对一个会话中的数据进行缓存
。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
基本上就是这样。这个简单语句的效果如下:
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef
注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
eviction可用的清除策略有:
默认的清除策略是 LRU。
flushInterval(刷新间隔)
属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置
,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)
属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024
。
readOnly(只读)
属性可以被设置为 true 或 false
。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时
,缓存会获得更新。
除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache
接口,且提供一个接受 String 参数作为 id 的构造器
。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file)
的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
你可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。 你也可以使用占位符(如 ${cache.file}
),以便替换成在配置文件属性中定义的值。
从版本 3.4.2 开始,MyBatis 已经支持在所有属性设置完毕之后,调用一个初始化方法
。 如果想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject
接口。
public interface InitializingObject {
void initialize() throws Exception;
}
提示 上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。
请注意,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中
。 因此,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成
。 默认情况下,语句会这样来配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
鉴于这是默认行为,显然你永远不应该以这样的方式显式配置一条语句。但如果你想改变默认的行为,只需要设置 flushCache 和 useCache 属性
。比如,某些情况下你可能希望特定 select 语句的结果排除于缓存之外,或希望一条 select 语句清空缓存。类似地,你可能希望某些 update 语句执行时不要刷新缓存。
cache-ref
回想一下上一节的内容,对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
当Session flush 或close 后, 该Session 中的所有Cache 将被清空
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
key:hashCode+查询的SqlId+编写的sql查询语句+参数
一级缓存失效的四种情况
本地缓存作用域属性配置localCacheScope
localCacheScope,本地缓存作用域。有两个值:
二级缓存(second level cache),全局作用域缓存。二级缓存默认不开启,需要手动配置。MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable
接口。二级缓存在SqlSession 关闭或提交之后才会生效
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。
这里需要注意的是,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句
,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
使用步骤
<setting name="cacheEnabled" value="true"/>
<cache />
mybatis对二级缓存的默认实现
mybatis对二级缓存有默认实现,使用map将数据存放在内存中,实现类为PerpetualCache。
package org.apache.ibatis.cache.impl; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import org.apache.ibatis.cache.Cache; import org.apache.ibatis.cache.CacheException; /** * @author Clinton Begin */ public class PerpetualCache implements Cache { private String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); } @Override public Object removeObject(Object key) { return cache.remove(key); } @Override public void clear() { cache.clear(); } @Override public ReadWriteLock getReadWriteLock() { return null; } @Override public boolean equals(Object o) { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } if (this == o) { return true; } if (!(o instanceof Cache)) { return false; } Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } @Override public int hashCode() { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } return getId().hashCode(); } }
cacheEnable
:配置二级缓存的开关。一级缓存一直是打开的。如果设置cacheEnable为false,则会关闭二级缓存配置这个select是否使用二级缓存。一级缓存一直是使用的
flushCache
属性:
增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
如果flushCache=true;每次查询之后都会清空缓存;缓存是没有被使用的;
sqlSession.clearCache()
只是用来清除一级缓存。
一级缓存默认开启,默认作用域:sqlSession
@Test public void testCacheOne(){ /*set auto commit ,which equals to the above*/ SqlSession session = MybatisUtils.getFactory().openSession(true); String statement = "com.web.mapper.userMapper.getUser"; /*return the effect rows*/ User user = session.selectOne(statement, 1); System.out.println("result.."+user); // session.clearCache(); //测试缓存肯定针对同样的数据,如果id变为2,缓存里面没有。缓存测试无从谈起。 user = session.selectOne(statement, 1); System.out.println("result.."+user); System.out.println("***************************************************"); }
缓存不清空时,连续查询:
@Test
public void testCacheOne(){
/*set auto commit ,which equals to the above*/
SqlSession session = MybatisUtils.getFactory().openSession(true);
String statement = "com.web.mapper.userMapper.getUser";
User user = session.selectOne(statement, 1);
System.out.println("result.."+user);
// session.clearCache();
user = session.selectOne(statement, 1);
System.out.println("result.."+user);
System.out.println("***************************************************");
}
result as follows :
如上图所示,只进行了一次数据库查询。第二次查询的时候,默认使用了缓存。
缓存清空,进行第二次查询:
@Test
public void testCacheOne(){
/*set auto commit ,which equals to the above*/
SqlSession session = MybatisUtils.getFactory().openSession(true);
String statement = "com.web.mapper.userMapper.getUser";
/*return the effect rows*/
User user = session.selectOne(statement, 1);
System.out.println("result.."+user);
session.clearCache();
user = session.selectOne(statement, 1);
System.out.println("result.."+user);
System.out.println("***************************************************");
}
result as follows :
如上图所示,两次都进行了数据库查询。
进行CUD操作,将默认清空缓存
session.update("com.web.mapper.userMapper.updateUser",new User(1, "mili", 21));
session.commit();
user = session.selectOne(statement, 1);
System.out.println("result.."+user);
result as follows :
如上图所示,进行第三地查询时,将重新查询数据库。
同样,当session关闭时,即 session.close( ) ,将会清空缓存。
当xml配置属性flushCache="true"
,也将不会使用缓存:
<!-- flushCache="true" useCache="false" -->
<select id="getUser" parameterType="int" resultMap="UserMap" flushCache="true">
select * from t_user where id=#{id}
</select>
result as follows :
综上,清空sqlSession缓存的六种方式 :
flushCache="true"
;userMapper.xml中启用二级缓存的简单方式:
<cache></cache>
对应源码如下:
public @interface CacheNamespace { Class<? extends org.apache.ibatis.cache.Cache> implementation() default PerpetualCache.class; Class<? extends org.apache.ibatis.cache.Cache> eviction() default LruCache.class; long flushInterval() default 0; int size() default 1024; boolean readWrite() default true; boolean blocking() default false; /** * Property values for a implementation object. * @since 3.4.2 */ Property[] properties() default {}; }
属性简单介绍如下:
映射语句文件中的所有select语句将会被缓存;
映射语句文件中的所有CUD操作将会刷新缓存;
缓存会默认使用LRU(Least Recently Used)算法来收回;
1. LRU – 最近最少使用的:移除最长时间不被使用的对象。
2. FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3. SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
4. WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
缓存会根据指定的时间间隔来刷新(默认情况下没有刷新间隔,缓存仅仅调用语句时刷新);
缓存会存储列表集合或对象(无论查询方法返回什么),默认存储1024个对象。
缓存会被视为是read/write(可读/可写)的缓存,意味着检索对象不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
实例如下 :
<mapper namespace="com.web.mapper.userMapper">
开启二级缓存:
<mapper namespace="com.web.mapper.userMapper">
<cache >
<property name="eviction" value="LRU"/>//回收策略为LRU
<property name="flushInterval" value="60000"/>//自动刷新时间间隔为60S
<property name="size" value="1024"/>//最多缓存1024个引用对象
<property name="readOnly" value="true"/>//只读
</cache>
如果想在命名空间中共享相同的缓存配置和实例,可以使用cache-ref 元素来引用另外一个缓存。
如下所示:
<cache-ref namespace="com.web.mapper.userMapper" />
//引用userMapper 命名空间中的cache。
提示
测试二级缓存
public void testCacheTwo(){ SqlSessionFactory factory = MybatisUtils.getFactory(); /*注意此处是从一个Factory 获取两个session*/ SqlSession session1 = factory.openSession(); SqlSession session2 = factory.openSession(); String statement = "com.web.mapper.userMapper.getUser"; /*return the effect rows*/ User user = session1.selectOne(statement, 1); System.out.println("result.."+user); //进行提交或者关闭将其数据写入二级缓存,否则将不会写入二级缓存 // session1.commit(); session1.close(); user = session2.selectOne(statement, 1); System.out.println("result.."+user); // session2.commit(); }
result as follows :
当执行以下方式时,将不会写入二级缓存:
① session.clearCache();
② update()(CUD操作会刷新二级缓存);
注意:
如果两个session不是从同一个Factory获取,那么二级缓存将不起作用。如下所示:
SqlSession sqlSession1 = MybatisUtils.getFactory().openSession();
SqlSession sqlSession2 = MybatisUtils.getFactory().openSession();
MyBatis SqlSession provides you with specific methods to handle transactions programmatically. But when using MyBatis-Spring your beans will be injected with a Spring managed SqlSession or a Spring managed mapper. That means that Spring will always handle your transactions.
mybatis sqlsession为您提供了以编程方式处理事务的特定方法。但是当使用MyBatis-Spring时,您的bean将被注入Spring管理的SqlSession或Spring管理的映射器。这意味着Spring将始终处理您的事务。
You cannot call SqlSession.commit(), SqlSession.rollback() or SqlSession.close() over a Spring managed SqlSession. If you try to do so, a UnsupportedOperationException exception will be thrown. Note these methods are not exposed in injected mapper classes.
你不能在 Spring 管理的 SqlSession 上调用 SqlSession.commit(),SqlSession.rollback() 或 SqlSession.close() 方法.如果这样做了,就会抛出 UnsupportedOperationException 异常。在使用注入的映射器时,这些方法也不会暴露出来。
Regardless of your JDBC connection’s autocommit setting, any execution of a SqlSession data method or any call to a mapper method outside a Spring transaction will be automatically committed.
无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。
spring结合mybatis后,一级缓存作用:
sqlSession
而创建新的sqlSession
,因此此时的一级缓存是不起作用的
ThreadLocal
获取当前资源绑定同一个sqlSession
,因此此时一级缓存是有效的
。一级缓存失效原因
mybatis
和spring
结合使用的时候,将原本的DefaultSqlSession
替换成了SqlSessionTemplate
,并且在SqlSessionTemplate
将sqlSession
替换成了sqlSessionProxy
代理对象,当我们执行sqlSession
的方法的时会调用到SqlSessionInterceptor
的invoke
()方法, 在invoke
()方法的fianlly
中调用了SqlSessionUtils.closeSqlSession()
方法将SqlSession
关闭了,所以一级缓存就会失效
了。
也就是说,spring
对mybatis
的SqlSession
的使用是由SqlSessionTemplate
控制的,在SqlSessionTemplate
类中执行SQL语句的SqlSession
都是通过sqlSessionProxy
来代理执行的,sqlSessionProxy
的生成是在构造函数中赋值。源码如下:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
可以看出sqlSessionProxy通过JDK的动态代理方法生成的一个代理类,在InvocationHandler对执行的方法进行了前后拦截,主要逻辑在invoke()方法中,invoke()方法中包含了了每次执行SQL语句时对SqlSesstion的操作:
SqlSessionUtils.getSqlSession()
),sqlSession.commit(true)
)SqlSessionUtils.closeSqlSession()
)。/** * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the * {@code PersistenceExceptionTranslator}. */ private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
SqlSessionUtils.getSqlSession方法源码如下
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); //在开启事务的情况之下,spring使用`ThreadLocal`获取当前资源绑定同一个`sqlSession`, //因此此时`一级缓存是有效的` SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession"); session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
代码解释如下:
ThreadLocal<Map<Object, Object>> resources
中获取SqlSessionHolderSqlSessionUtils.sessionHolder方法源码如下:
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
SqlSession session = null;
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException(
"Cannot change the ExecutorType when there is an existing transaction");
}
holder.requested();
LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
session = holder.getSqlSession();
}
return session;
}
代码解释如下:
synchronizedWithTransaction
属性是否为trueMyBatis与Spring整合官网地址:http://www.mybatis.org/spring/transactions.html
MyBatis官网文档地址:http://www.mybatis.org/mybatis-3/
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
MyBatis定义了Cache接口方便我们进行自定义扩展。
整合EhCache步骤:
ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar、slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="D:\ehcache" /> <defaultCache maxElementsInMemory="10000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
参照缓存:若想在命名空间中共享相同的缓存配置和实例。可以使用cache-ref 元素来引用另外一个缓存。
属性 | 描述 |
---|---|
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(先进先出) |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。