mybatis篇
- mybatis-獨(dú)立使用
- mybatis之TypeHandler
- mybatis一級(jí)緩存原理
- mybatis二級(jí)緩存原理
- mybatis插件原理解析
- mybatis整合spring
一級(jí)緩存的作用域是Sqlsession級(jí)別的顽爹,也就是說不同的Sqlsession是不會(huì)走一級(jí)緩存的啃匿,那么如果需要跨Sqlsession的緩存颠印,就需要使用到二級(jí)緩存了。
二級(jí)緩存的話默認(rèn)是關(guān)閉的睛廊,所以需要我們開啟已艰,開啟的方式官網(wǎng)也有介紹,需要在mybatis-config.xml核心配置文件中開啟二級(jí)緩存功能祟滴,并且我們mapper.xml中也需要加入<cache/>標(biāo)簽刊懈,二者缺一不可手幢,后面我們看源碼就能知道為啥這兩個(gè)缺一不可捷凄。
<settings>
<!-- 二級(jí)緩存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
<cache/>
先來看個(gè)例子
public void cache() {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
MybatisUserinfoMapper userinfoMapper = sqlSession.getMapper(MybatisUserinfoMapper.class);
MybatisUserinfoModel model = userinfoMapper.selectByKey(4);
System.out.println(model);
System.out.println("=====================");
// 執(zhí)行了查詢語句
SqlSession session = sqlSessionFactory.openSession();
MybatisUserinfoMapper mapper = session.getMapper(MybatisUserinfoMapper.class);
MybatisUserinfoModel userinfoModel = mapper.selectByKey(4);
System.out.println(userinfoModel);
session.close();
sqlSession.close();
}
執(zhí)行結(jié)果很意外,為什么二級(jí)緩存的功能都開啟了围来,結(jié)果sql還是執(zhí)行了2次跺涤,并沒有走緩存,其實(shí)监透,二級(jí)緩存還有一個(gè)要注意的點(diǎn)那就是必須要提交事務(wù)二級(jí)緩存才會(huì)保存記錄桶错,因?yàn)橐呀?jīng)是跨SqlSession共享緩存了,所以事務(wù)必須要提交胀蛮,否則會(huì)讀取到因混滾導(dǎo)致的錯(cuò)誤數(shù)據(jù)院刁。
public void cache() {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
MybatisUserinfoMapper userinfoMapper = sqlSession.getMapper(MybatisUserinfoMapper.class);
MybatisUserinfoModel model = userinfoMapper.selectByKey(4);
System.out.println(model);
System.out.println("=====================");
// 需要提交事務(wù)
sqlSession.commit();
// 執(zhí)行了查詢語句
SqlSession session = sqlSessionFactory.openSession();
MybatisUserinfoMapper mapper = session.getMapper(MybatisUserinfoMapper.class);
MybatisUserinfoModel userinfoModel = mapper.selectByKey(4);
System.out.println(userinfoModel);
session.close();
sqlSession.close();
}
可以看到,第二次查詢沒有走sql,直接從二級(jí)緩存中獲取的值粪狼。
這里我們就不再具體分析sqlsession的獲取和mapper的獲取了退腥,具體可以看之前的文章mybatis一級(jí)緩存原理
有個(gè)地方需要注意,二級(jí)緩存的Sqlsession中的Executor實(shí)際上是CachingExecutor
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
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(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 這就是必須要配置cacheEnabled的值再榄,否則不會(huì)存在二級(jí)緩存
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
我們知道getMapper最終的執(zhí)行都會(huì)走到MapperProxy類中的invoker方法狡刘,具體就來分析這個(gè)類。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
最后來到了重點(diǎn)的地方
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
CacheKey我們可以認(rèn)為他就是每個(gè)方法對應(yīng)的一個(gè)唯一標(biāo)識(shí)符困鸥。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// mapper.xml中我們配置的<cache/>標(biāo)簽
Cache cache = ms.getCache();
if (cache != null) {
// 如果flushCache配置為了true颓帝,那么就會(huì)清空一級(jí)緩存和二級(jí)緩存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
這里我們就可以看出為什么之前兩者必須要配置,cacheEnable開啟了才會(huì)用CachingExecutor包裝一下BaseExecutor窝革,而<cache/>標(biāo)簽只有配置了才會(huì)走緩存的邏輯
List<E> list = (List<E>) tcm.getObject(cache, key);
這里的tcm
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit;
private final Map<Object, Object> entriesToAddOnCommit;
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, Object>();
this.entriesMissedInCache = new HashSet<Object>();
}
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
到這,我們就差不多揭開了二級(jí)緩存的秘密吕座,重要的還是<cache/>這個(gè)標(biāo)簽虐译,因?yàn)樗拇嬖诰蛯?yīng)著每個(gè)mapper.xml中的一個(gè)具體Cache類,而這個(gè)類在每個(gè)mapper.xml中又是同一個(gè)吴趴,所以最終的值是放入了Cache類中漆诽,key為CacheKey,value就是sql執(zhí)行的結(jié)果锣枝。
至于為什么需要事務(wù)提交才能命中二級(jí)緩存厢拭,我們看下put方法就知道
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
這里的putObject并沒有真正的把值存入Cache中,而是存入了待提交的Map中撇叁,所以再來看下commit做了什么
@Override
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
具體看tcm.commit()
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
而這里可以看到此處會(huì)遍歷所有的TransactionCache并執(zhí)行commit方法
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
真相就出來了供鸠,會(huì)遍歷待提交的Map然后把里面的值都存入Cache中,所以后面的查詢就能直接從Cache中拿到值了陨闹。
總結(jié)
二級(jí)緩存先會(huì)把Sqlsession中的Executor包裝成包裝成CacheingExecutor,所有的sql都會(huì)經(jīng)過這個(gè)類楞捂,而該類通過mapper.xml中配置的唯一<cache/>標(biāo)簽生成的Cache類存放每個(gè)方法執(zhí)行的結(jié)果