查詢緩存主要是爲了提升查詢訪問速度,即當用戶執行一次查詢後,會將該數據結果放到緩存中,當下次再執行此查詢時就不會訪問數據庫了而是直接從緩存中獲取該數據。 若是在緩存中找到了數據那叫作命中。sql
@Test
public void testLocalCache() throws Exception {
SqlSession sqlSession = factory.openSession(); // 自動提交事務
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1));
// 第二三次會從緩存中拿數據,不查數據庫
System.out.println(studentMapper.getStudentById(1));
System.out.println(studentMapper.getStudentById(1));
sqlSession.close();
}
複製代碼
@Test
public void testLocalCacheClear() throws Exception {
SqlSession sqlSession = factory.openSession(true); // 自動提交事務
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1));
// 增刪改會清空緩存
System.out.println("增長了" + studentMapper.addStudent(buildStudent()) + "個學生");
// 會從數據庫查數據
System.out.println(studentMapper.getStudentById(1));
sqlSession.close();
}
複製代碼
對SqlSession的操做mybatis內部都是經過Executor來執行的。Executor的生命週期和SqlSession是一致的。Mybatis在Executor中建立了一級緩存,基於PerpetualCache 類的 HashMap數據庫
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
// 執行器
private Executor executor;
private boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
}
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 緩存實例
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.configuration = configuration; this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.closed = false; this.wrapperExecutor = this;
//mybatis一級緩存,在建立SqlSession->Executor時候動態建立,隨着sqlSession銷燬而銷燬
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
}
}
// 緩存實現類
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
}
複製代碼
//SqlSession.selectList會調用此方法(一級緩存操做,老是先查詢一級緩存,緩存中不存在再查詢數據庫)
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
//若是已經關閉,報錯
throw new ExecutorException("Executor was closed.");
}
//先清一級緩存,再查詢,但僅僅查詢堆棧爲0才清,爲了處理遞歸調用
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
//加一,這樣遞歸調用到上面的時候就不會再清局部緩存了
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//若是查到localCache緩存,處理
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//從數據庫查
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
finally {
queryStack--; //清空堆棧
}
if (queryStack == 0) {
//延遲加載隊列中全部元素
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); //清空延遲加載隊列
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
複製代碼
localCache 緩存的key 爲CacheKey對象 CacheKey:statementId + rowBounds + 傳遞給JDBC的SQL + 傳遞給JDBC的參數值apache
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
複製代碼
<delete id="deleteStudent" flushCache="false">
DELETE FROM t_student where id=#{id}
</delete>
複製代碼
// mybatis-config.xml 中配置
<settings>
<setting name="localCacheScope" value="SESSION"/>
默認值爲 true。即二級緩存默認是開啓的
<setting name="cacheEnabled" value="true"/>
</settings>
// 具體mapper.xml 中配置
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
<!-- 開啓本mapper的namespace下的二級緩存
type:指定cache接口的實現類的類型,mybatis默認使用PerpetualCache
要和ehcache整合,須要配置type爲ehcache實現cache接口的類型-->
<cache />
<!-- 下面的一些SQL語句暫時略 -->
</mapper>
複製代碼
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
//確保ExecutorType不爲空(defaultExecutorType有可能爲空)
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);
}
//重點在這裏,若是啓用二級緩存,返回Executor的Cache包裝類對象
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
複製代碼
靜態代理模式。在CachingExecutor的全部操做都是經過調用內部的delegate對象執行的。緩存只應用於查詢緩存
public class CachingExecutor implements Executor {
private Executor delegate;
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//是否須要更緩存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, 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);
}
}
複製代碼
@Test
public void testCacheWithCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true); // 自動提交事務
SqlSession sqlSession2 = factory.openSession(true); // 自動提交事務
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
//sqlSession1關閉後,會將sqlsession1中的數據寫到二級緩存區域
//不關閉的話不會寫入二級緩存
sqlSession1.close();
System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}
複製代碼
@Test
public void testCacheWithoutCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true); // 自動提交事務
SqlSession sqlSession2 = factory.openSession(true); // 自動提交事務
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
//sqlSession未關閉,不會將數據寫到二級緩存區域,會從數據庫中查詢
System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}
複製代碼
<setting name="cacheEnabled" value="false"/>
複製代碼
<select id="selectStudentById" useCache="false" resultMap="studentMapper">
SELECT id,name,age,score,password FROM t_student where id=#{id}
</select>
複製代碼
在一個命名空間下使用二級緩存 二級緩存對於不一樣的命名空間namespace的數據是互不干擾的,假若多個namespace中對一個表進行操做的話,就會致使這不一樣的namespace中的數據不一致的狀況。bash
在單表上使用二級緩存 在作關聯關係查詢時,就會發生多表的操做,此時有可能這些表存在於多個namespace中,這就會出現上一條內容出現的問題了。session
查詢多於修改時使用二級緩存 在查詢操做遠遠多於增刪改操做的狀況下可使用二級緩存。由於任何增刪改操做都將刷新二級緩存,對二級緩存的頻繁刷新將下降系統性能。mybatis