Mybatis源码-缓存机制
积累知识,胜过积蓄金银!毕竟在##column_title##开发的过程中,会遇到各种各样的问题,往往都是一些细节知识点还没有掌握好而导致的,因此基础知识点的积累是很重要的。下面本文《Mybatis源码-缓存机制》,就带大家讲解一下MySQL、缓存、jdbc、mybatis、缓存设计知识点,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~
正文
一. 一级缓存机制展示
在
<settings><setting name="localCacheScope" value="SESSION"></setting></settings>
其中localCacheScope可以配置为SESSION(默认)或者STATEMENT,含义如下所示。
| 属性值 | 含义 |
|---|---|
| SESSION | 一级缓存在一个会话中生效。即在一个会话中的所有查询语句,均会共享同一份一级缓存,不同会话中的一级缓存不共享。 |
| STATEMENT | 一级缓存仅针对当前执行的<settings><setting name="logImpl" value="STDOUT_LOGGING"></setting><setting name="cacheEnabled" value="false"></setting><setting name="localCacheScope" value="SESSION"></setting></settings> 映射接口如下所示。 public interface BookMapper {
Book selectBookById(int id);
}映射文件如下所示。 <?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.mybatis.learn.dao.BookMapper"><resultmap id="bookResultMap" type="com.mybatis.learn.entity.Book"><result column="b_name" property="bookName"></result><result column="b_price" property="bookPrice"></result></resultmap><select id="selectBookById" resultmap="bookResultMap">
SELECT
b.id, b.b_name, b.b_price
FROM
book b
WHERE
b.id=#{id}
</select></mapper>public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
SqlSession sqlSession = sqlSessionFactory.openSession(false);
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
System.out.println(bookMapper.selectBookById(1));
System.out.println(bookMapper.selectBookById(1));
System.out.println(bookMapper.selectBookById(1));
}
}在执行代码中,连续执行了三次查询操作,看一下日志打印,如下所示。 ![]() 可以知道,只有第一次查询时和数据库进行了交互,后面两次查询均是从一级缓存中查询的数据。现在往映射接口和映射文件中加入更改数据的逻辑,如下所示。 public interface BookMapper {
Book selectBookById(int id);
// 根据id更改图书价格
void updateBookPriceById(@Param("id") int id, @Param("bookPrice") float bookPrice);
}<?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.mybatis.learn.dao.BookMapper"><resultmap id="bookResultMap" type="com.mybatis.learn.entity.Book"><result column="b_name" property="bookName"></result><result column="b_price" property="bookPrice"></result></resultmap><select id="selectBookById" resultmap="bookResultMap">
SELECT
b.id, b.b_name, b.b_price
FROM
book b
WHERE
b.id=#{id}
</select><insert id="updateBookPriceById">
UPDATE
book
SET
b_price=#{bookPrice}
WHERE
id=#{id}
</insert></mapper>执行的操作为先执行一次查询操作,然后执行一次更新操作并提交事务,最后再执行一次查询操作,执行代码如下所示。 public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
SqlSession sqlSession = sqlSessionFactory.openSession(false);
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
System.out.println(bookMapper.selectBookById(1));
System.out.println("Change database.");
bookMapper.updateBookPriceById(1, 22.5f);
sqlSession.commit();
System.out.println(bookMapper.selectBookById(1));
}
}执行结果如下所示。 ![]() 通过上述结果可以知道,在执行更新操作之后,再执行查询操作时,是直接从数据库查询的数据,并未使用一级缓存,即在一个会话中,对数据库的增,删,改操作,均会使一级缓存失效。 现在在执行代码中创建两个会话,先让会话1执行一次查询操作,然后让会话2执行一次更新操作并提交事务,最后让会话1再执行一次相同的查询。执行代码如下所示。 public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);
System.out.println(bookMapper1.selectBookById(1));
System.out.println("Change database.");
bookMapper2.updateBookPriceById(1, 22.5f);
sqlSession2.commit();
System.out.println(bookMapper1.selectBookById(1));
}
}执行结果如下所示。 ![]() 上述结果表明,会话1的第一次查询是直接查询的数据库,然后会话2执行了一次更新操作并提交了事务,此时数据库中id为1的图书的价格已经变更为了22.5,紧接着会话1又做了一次查询,但查询结果中的图书价格为20.5,说明会话1的第二次查询是从缓存获取的查询结果。所以在这里可以知道, @Override
public <e> List<e> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
// 获取Sql语句
BoundSql boundSql = ms.getBoundSql(parameter);
// 生成CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用重载的query()方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}</e></e>在上述 public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLIER;
this.count = 0;
this.updateList = new ArrayList();
}同时hashcode,checksum,count和updateList字段会在 public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}主要逻辑就是基于 public CacheKey createCacheKey(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, BoundSql boundSql) {
......
// 创建CacheKey
CacheKey cacheKey = new CacheKey();
// 基于MappedStatement的id更新CacheKey
cacheKey.update(ms.getId());
// 基于RowBounds的offset更新CacheKey
cacheKey.update(rowBounds.getOffset());
// 基于RowBounds的limit更新CacheKey
cacheKey.update(rowBounds.getLimit());
// 基于Sql语句更新CacheKey
cacheKey.update(boundSql.getSql());
......
// 基于查询参数更新CacheKey
cacheKey.update(value);
......
// 基于Environment的id更新CacheKey
cacheKey.update(configuration.getEnvironment().getId());
return cacheKey;
}所以可以得出结论,判断 @Override
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.");
}
// queryStack是BaseExecutor的成员变量
// queryStack主要用于递归调用query()方法时防止一级缓存被清空
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<e> list;
try {
queryStack++;
// 先从一级缓存中根据CacheKey命中查询结果
list = resultHandler == null ? (List<e>) localCache.getObject(key) : null;
if (list != null) {
// 处理存储过程相关逻辑
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 未命中,则直接查数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
// 如果一级缓存作用范围是STATEMENT时,每次query()执行完毕就需要清空一级缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}</e></e></e></e>上述 private <e> List<e> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<e> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用doQuery()进行查询操作
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 将查询结果添加到一级缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
// 返回查询结果
return list;
}</e></e></e>@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource())
.activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 执行操作前先清空缓存
clearLocalCache();
return doUpdate(ms, parameter);
}所以 <settings><setting name="logImpl" value="STDOUT_LOGGING"></setting><setting name="cacheEnabled" value="true"></setting><setting name="localCacheScope" value="STATEMENT"></setting></settings> 上述配置文件中还将一级缓存的作用范围设置为了STATEMENT,目的是为了在例子中屏蔽一级缓存对查询结果的干扰。映射接口如下所示。 public interface BookMapper {
Book selectBookById(int id);
void updateBookPriceById(@Param("id") int id, @Param("bookPrice") float bookPrice);
}要使用二级缓存,还需要在映射文件中加入二级缓存相关的设置,如下所示。 <?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.mybatis.learn.dao.BookMapper"><!-- 二级缓存相关设置 --><cache eviction="LRU" type="org.apache.ibatis.cache.impl.PerpetualCache" flushinterval="600000" size="1024" readonly blocking="false"></cache><resultmap id="bookResultMap" type="com.mybatis.learn.entity.Book"><result column="b_name" property="bookName"></result><result column="b_price" property="bookPrice"></result></resultmap><select id="selectBookById" resultmap="bookResultMap">
SELECT
b.id, b.b_name, b.b_price
FROM
book b
WHERE
b.id=#{id}
</select><insert id="updateBookPriceById">
UPDATE
book
SET
b_price=#{bookPrice}
WHERE
id=#{id}
</insert></mapper>二级缓存相关设置的每一项的含义,会在本小节末尾进行说明。 场景一:创建两个会话,会话1以相同 public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);
System.out.println(bookMapper1.selectBookById(1));
System.out.println(bookMapper1.selectBookById(1));
System.out.println(bookMapper2.selectBookById(1));
}
}执行结果如下所示。 ![]() public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);
System.out.println(bookMapper1.selectBookById(1));
sqlSession1.commit();
System.out.println(bookMapper1.selectBookById(1));
System.out.println(bookMapper2.selectBookById(1));
}
}执行结果如下所示。 ![]() 场景二中第一次查询后提交了事务,此时将查询结果缓存到了二级缓存,所以后续的查询全部在二级缓存中命中了查询结果。 场景三:创建两个会话,会话1执行一次查询并提交事务,然后会话2执行一次更新并提交事务,接着会话1再执行一次相同的查询。执行代码如下所示。 public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
// 将事务隔离级别设置为读已提交
SqlSession sqlSession1 = sqlSessionFactory.openSession(
TransactionIsolationLevel.READ_COMMITTED);
SqlSession sqlSession2 = sqlSessionFactory.openSession(
TransactionIsolationLevel.READ_COMMITTED);
BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);
System.out.println(bookMapper1.selectBookById(1));
sqlSession1.commit();
System.out.println("Change database.");
bookMapper2.updateBookPriceById(1, 20.5f);
sqlSession2.commit();
System.out.println(bookMapper1.selectBookById(1));
}
}执行结果如下所示。 ![]() 场景三的执行结果表明,执行更新操作并且提交事务后,会清空二级缓存,执行新增和删除操作也是同理。 场景四:创建两个会话,创建两张表,会话1首先执行一次多表查询并提交事务,然后会话2执行一次更新操作以更新表2的数据并提交事务,接着会话1再执行一次相同的多表查询。创表语句如下所示。 CREATE TABLE book(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
b_name VARCHAR(255) NOT NULL,
b_price FLOAT NOT NULL,
bs_id INT(11) NOT NULL,
FOREIGN KEY book(bs_id) REFERENCES bookstore(id)
);
CREATE TABLE bookstore(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
bs_name VARCHAR(255) NOT NULL
)往book表和bookstore表中添加如下数据。 INSERT INTO book (b_name, b_price, bs_id) VALUES ("Math", 20.5, 1);
INSERT INTO book (b_name, b_price, bs_id) VALUES ("English", 21.5, 1);
INSERT INTO book (b_name, b_price, bs_id) VALUES ("Water Margin", 30.5, 2);
INSERT INTO bookstore (bs_name) VALUES ("XinHua");
INSERT INTO bookstore (bs_name) VALUES ("SanYou")创建 @Data
public class BookStore {
private String id;
private String bookStoreName;
}创建 @Data
public class BookDetail {
private long id;
private String bookName;
private float bookPrice;
private BookStore bookStore;
}public interface BookMapper {
Book selectBookById(int id);
void updateBookPriceById(@Param("id") int id, @Param("bookPrice") float bookPrice);
BookDetail selectBookDetailById(int id);
}<?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.mybatis.learn.dao.BookMapper"><cache eviction="LRU" type="org.apache.ibatis.cache.impl.PerpetualCache" flushinterval="600000" size="1024" readonly blocking="false"></cache><resultmap id="bookResultMap" type="com.mybatis.learn.entity.Book"><result column="b_name" property="bookName"></result><result column="b_price" property="bookPrice"></result></resultmap><resultmap id="bookDetailResultMap" type="com.mybatis.learn.entity.BookDetail"><id column="id" property="id"></id><result column="b_name" property="bookName"></result><result column="b_price" property="bookPrice"></result><association property="bookStore"><id column="id" property="id"></id><result column="bs_name" property="bookStoreName"></result></association></resultmap><select id="selectBookById" resultmap="bookResultMap">
SELECT
b.id, b.b_name, b.b_price
FROM
book b
WHERE
b.id=#{id}
</select><insert id="updateBookPriceById">
UPDATE
book
SET
b_price=#{bookPrice}
WHERE
id=#{id}
</insert><select id="selectBookDetailById" resultmap="bookDetailResultMap">
SELECT
b.id, b.b_name, b.b_price, bs.id, bs.bs_name
FROM
book b, bookstore bs
WHERE
b.id=#{id}
AND
b.bs_id = bs.id
</select></mapper>还需要添加 public interface BookStoreMapper {
void updateBookPriceById(@Param("id") int id, @Param("bookStoreName") String bookStoreName);
}还需要添加 <?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.mybatis.learn.dao.BookStoreMapper"><cache eviction="LRU" type="org.apache.ibatis.cache.impl.PerpetualCache" flushinterval="600000" size="1024" readonly blocking="false"></cache><insert id="updateBookPriceById">
UPDATE
bookstore
SET
bs_name=#{bookStoreName}
WHERE
id=#{id}
</insert></mapper>进行完上述更改之后,进行场景四的测试,执行代码如下所示。 public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
// 将事务隔离级别设置为读已提交
SqlSession sqlSession1 = sqlSessionFactory.openSession(
TransactionIsolationLevel.READ_COMMITTED);
SqlSession sqlSession2 = sqlSessionFactory.openSession(
TransactionIsolationLevel.READ_COMMITTED);
BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
BookStoreMapper bookStoreMapper = sqlSession2.getMapper(BookStoreMapper.class);
System.out.println(bookMapper1.selectBookDetailById(1));
sqlSession1.commit();
System.out.println("Change database.");
bookStoreMapper.updateBookStoreById(1, "ShuXiang");
sqlSession2.commit();
System.out.println(bookMapper1.selectBookDetailById(1));
}
}执行结果如下所示。 ![]() 会话1第一次执行多表查询并提交事务时,将查询结果缓存到了二级缓存中,然后会话2对bookstore表执行了更新操作并提交了事务,但是最后会话1第二次执行相同的多表查询时,却从二级缓存中命中了查询结果,最终导致查询出来了脏数据。实际上,二级缓存的作用范围是同一命名空间下的多个会话共享,这里的命名空间就是映射文件的namespace,可以理解为每一个映射文件持有一份二级缓存,所有会话在这个映射文件中的所有操作,都会共享这个二级缓存。所以场景四的例子中,会话2对bookstore表执行更新操作并提交事务时,清空的是 <?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.mybatis.learn.dao.BookStoreMapper"><cache-ref namespace="com.mybatis.learn.dao.BookMapper"></cache-ref><insert id="updateBookStoreById">
UPDATE
bookstore
SET
bs_name=#{bookStoreName}
WHERE
id=#{id}
</insert></mapper>执行代码如下所示。 public class MybatisTest {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(resource));
// 将事务隔离级别设置为读已提交
SqlSession sqlSession1 = sqlSessionFactory.openSession(
TransactionIsolationLevel.READ_COMMITTED);
SqlSession sqlSession2 = sqlSessionFactory.openSession(
TransactionIsolationLevel.READ_COMMITTED);
BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
BookStoreMapper bookStoreMapper = sqlSession2.getMapper(BookStoreMapper.class);
System.out.println(bookMapper1.selectBookDetailById(1));
sqlSession1.commit();
System.out.println("Change database.");
bookStoreMapper.updateBookStoreById(1, "ShuXiang");
sqlSession2.commit();
System.out.println(bookMapper1.selectBookDetailById(1));
}
}执行结果如下所示。 ![]() 在 private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>标签
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>标签
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '"
+ resource + "'. Cause: " + e, e);
}
}</cache></cache-ref>在 private void cacheElement(XNode context) {
if (context != null) {
// 获取<cache>标签的type属性值
String type = context.getStringAttribute("type", "PERPETUAL");
Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 获取<cache>标签的eviction属性值
String eviction = context.getStringAttribute("eviction", "LRU");
Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 获取<cache>标签的flushInterval属性值
Long flushInterval = context.getLongAttribute("flushInterval");
// 获取<cache>标签的size属性值
Integer size = context.getIntAttribute("size");
// 获取<cache>标签的readOnly属性值并取反
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 获取<cache>标签的blocking属性值
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}</cache></cache></cache></cache></cache></cache>单步跟踪 public Cache useNewCache(Class extends Cache> typeClass,
Class extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}在 public CacheBuilder(String id) {
this.id = id;
this.decorators = new ArrayList();
}所以可以知道, public Cache build() {
setDefaultImplementations();
// 创建PerpetualCache,作为基础Cache对象
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
if (PerpetualCache.class.equals(cache.getClass())) {
// 为基础Cache对象添加缓存淘汰策略相关的装饰器
for (Class extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
// 继续添加装饰器
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}<cache eviction="LRU" type="org.apache.ibatis.cache.impl.PerpetualCache" flushinterval="600000" size="1024" readonly blocking="true"></cache> 那么生成的二级缓存对象如下所示。 ![]() 整个装饰链如下图所示。 ![]() 现在回到 public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}这里就印证了前面的猜想,即二级缓存 private void cacheRefElement(XNode context) {
if (context != null) {
// 在Configuration的cacheRefMap中将当前映射文件命名空间与引用的映射文件命名空间建立映射关系
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// CacheRefResolver会将引用的映射文件的二级缓存从Configuration中获取出来并赋值给MapperBuilderAssistant的currentCache
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}@Override
public <e> List<e> query(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取Sql语句
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建CacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}</e></e>继续看重载的 @Override
public <e> List<e> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 从MappedStatement中将二级缓存获取出来
Cache cache = ms.getCache();
if (cache != null) {
// 清空二级缓存(如果需要的话)
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 处理存储过程相关逻辑
ensureNoOutParams(ms, boundSql);
// 从二级缓存中根据CacheKey命中查询结果
List<e> list = (List<e>) tcm.getObject(cache, key);
if (list == null) {
// 未命中缓存,则查数据库
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 将从数据库查询到的结果缓存到二级缓存中
tcm.putObject(cache, key, list);
}
// 返回查询结果
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}</e></e></e></e>上述 public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}通过上述代码可以知道,一个二级缓存对应一个 @Override
public Object getObject(Object key) {
// 在二级缓存中命中查询结果
Object object = delegate.getObject(key);
if (object == null) {
// 未命中则将CacheKey添加到entriesMissedInCache中
// 用于统计命中率
entriesMissedInCache.add(key);
}
if (clearOnCommit) {
return null;
} else {
return object;
}
}到这里就可以知道了,在 private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}调用 @Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}现在继续分析为什么将查询结果缓存到二级缓存中需要事务提交。从数据库中查询出来结果后, public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}继续看 @Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}到这里就搞明白了,在事务提交之前,查询结果会被暂存到 @Override
public void commit() {
commit(false);
}
@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);
// 调用TransactionalCacheManager的commit()方法
tcm.commit();
}在 public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
// 调用TransactionalCache的commit()方法
txCache.commit();
}
}继续看 public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
private void flushPendingEntries() {
// 将entriesToAddOnCommit中暂存的查询结果全部缓存到二级缓存中
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);
}
}
}</object>至此可以知道,当调用 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);即如果没有在CURD标签中显式的设置flushCache属性,则会给flushCache字段一个默认值,且默认值为非查询标签下默认为true,所以到这里就可以知道,如果是增,删,改操作,那么 TransactionalCache中的clearOnCommit字段会被置为true,从而在提交事务时会在 TransactionalCache的 commit()方法中将二级缓存清空。 到这里,二级缓存的源码分析结束。二级缓存的使用流程可以用下图进行概括,如下所示。 ![]() 总结关于 Mybatis的一级缓存,总结如下。
关于 Mybatis的二级缓存,总结如下。
好了,本文到此结束,带大家了解了《Mybatis源码-缓存机制》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多数据库知识! |
Apache Doris ODBC Mysql外表在Ubuntu下使用方法及配置
- 上一篇
- Apache Doris ODBC Mysql外表在Ubuntu下使用方法及配置
- 下一篇
- 完蛋,我的事务怎么不生效?
-
- 数据库 · MySQL | 1天前 |
- MySQL数值函数大全及使用技巧
- 117浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- 三种登录MySQL方法详解
- 411浏览 收藏
-
- 数据库 · MySQL | 3天前 |
- MySQL数据备份方法与工具推荐
- 420浏览 收藏
-
- 数据库 · MySQL | 3天前 |
- MySQL数据备份方法与工具推荐
- 264浏览 收藏
-
- 数据库 · MySQL | 4天前 |
- MySQL索引的作用是什么?
- 266浏览 收藏
-
- 数据库 · MySQL | 5天前 |
- MySQL排序原理与实战应用
- 392浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQLwhere条件查询技巧
- 333浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL常用数据类型有哪些?怎么选更合适?
- 234浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL常用命令大全管理员必学30条
- 448浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL高效批量插入数据方法大全
- 416浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL性能优化技巧大全
- 225浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL数据备份4种方法保障安全
- 145浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3165次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3377次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3406次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4510次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3786次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览
-
- mysql面试题
- 2023-01-17 157浏览
-
- MySQL数据表简单查询
- 2023-01-10 101浏览












