警告
本文最后更新于 2020-12-30,文中内容可能已过时。
mybatis源码系列文章,示例中带有中文注释的源码 copy 自 https://gitee.com/wlizhi/mybatis-3
链接中源码是作者从 github 下载,并以自身理解对核心流程及主要节点做了详细的中文注释。
1 缓存模块概要
mybatis 的缓存模块是基于 Map 的,从缓存中读写数据是其基础功能。
mybatis 提供了许多缓存的实现。如阻塞式缓存、先进先出缓存、日志缓存、缓存清空策略、软引用缓存、虚引用缓存、具有同步控制的缓存等。
这些缓存实现可以任意的组合优雅的附加到基础功能上。
如果使用继承、或动态代理的方式很难做到任意的组合(会有大量子类)。继承或动态代理方式在实现类的组合上来说是静态的,这是 mybatis 缓存模块实现的最大难题。
使用装饰器模式可以解决此问题,mybatis 正是使用了装饰器模式实现的缓存模块。
2 装饰器模式
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
jdk 中 io 流就是典型的装饰器模式。
类似这样:new BufferedInputStream(new FileInputStream("xx.txt"))
使用装饰类,持有原始类的方式,对其进行功能增强。如果需要多层的装饰,则创建装饰实例,然后作为参数传递给下一个装饰实例即可。
这样可以做到任意个数、任意顺序的装饰。装饰本身即是功能的增强。装饰类本身与原始类实现同一个接口/继承了同一个类。
上面示例中,BufferedInputStream、FileInputStream 实际上都实现了 InputStream。
Collections 类,对集合进行增强的一些静态方法,也是使用了装饰器模式。
3 缓存key的创建方式 - CacheKey
CacheKey 是对 mybatis 缓存的 key 值的封装。由于缓存时的 key 是根据多种元素来生成 key 的,这里需要通过一定的算法来进行封装,
生成 hashcode 和 equals。其中关键方法有 update()、hashCode()、equals()。
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class CacheKey implements Cloneable , Serializable {
private static final long serialVersionUID = 1146682552656046210L ;
private static final int DEFAULT_MULTIPLIER = 37 ;
private static final int DEFAULT_HASHCODE = 17 ;
/** 参与 hash 计算的乘数 */
private final int multiplier ;
/** CacheKey 的 hash 值,在 update 函数中运算出来的 */
private int hashcode ;
/** 校验和,hash值的和 */
private long checksum ;
/** 参与计算元素的个数 */
private int count ;
// 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this
// is not always true and thus should not be marked transient.
/** 该集合中的元素决定两个 CacheKey 是否相等 */
private List < Object > updateList ;
public CacheKey () {
this . hashcode = DEFAULT_HASHCODE ;
this . multiplier = DEFAULT_MULTIPLIER ;
this . count = 0 ;
this . updateList = new ArrayList <> ();
}
public CacheKey ( Object [] objects ) {
this ();
updateAll ( objects );
}
public int getUpdateCount () {
return updateList . size ();
}
public void update ( Object object ) {
// 获取object的hash值
int baseHashCode = object == null ? 1 : ArrayUtil . hashCode ( object );
// 更新count、checksum以及hashcode的值
count ++ ;
checksum += baseHashCode ;
baseHashCode *= count ;
hashcode = multiplier * hashcode + baseHashCode ;
// 将对象添加到updateList中
updateList . add ( object );
}
public void updateAll ( Object [] objects ) {
for ( Object o : objects ) {
update ( o );
}
}
@Override
public boolean equals ( Object object ) {
// 比较是不是同一个对象
if ( this == object ) {
return true ;
}
// 是否类型相同
if ( ! ( object instanceof CacheKey )) {
return false ;
}
final CacheKey cacheKey = ( CacheKey ) object ;
// hashcode 是否相同
if ( hashcode != cacheKey . hashcode ) {
return false ;
}
// checksum 是否相同
if ( checksum != cacheKey . checksum ) {
return false ;
}
// count是否相同
if ( count != cacheKey . count ) {
return false ;
}
// 以上都相同,才按顺序比较 updateList 中元素是否相等,只有全部相等,才返回 true。
for ( int i = 0 ; i < updateList . size (); i ++ ) {
Object thisObject = updateList . get ( i );
Object thatObject = cacheKey . updateList . get ( i );
if ( ! ArrayUtil . equals ( thisObject , thatObject )) {
return false ;
}
}
return true ;
}
@Override
public int hashCode () {
return hashcode ;
}
}
CacheKey 是通过 MapperStatement 的id、offset、limit、以及 sql 中占位符的参数。来计算出对应的 hashcode,并且会将这些信息封装在一个 List 中。
在进行比较的时候,会先进行 hashcode、checksum 的比较,之后对封装的列表中的元素依次比较,如果全部相等,返回true,中途有一个比较不成立,则返回false。
这么做是为了提高效率。
hashcode 和 checkSum 的生成,是根据 updateList 中的元素计算的。
关键的成员变量:
hashcode:CacheKey 的 hash 值,在 update 函数中实时运算出来的
checksum:校验和,hash值的和
count:参与计算元素的个数
updateList:该集合中的元素决定两个 CacheKey 是否相等
cacheKey 的创建源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public abstract class BaseExecutor implements Executor {
@Override
public CacheKey createCacheKey ( MappedStatement ms , Object parameterObject , RowBounds rowBounds , BoundSql boundSql ) {
if ( closed ) {
throw new ExecutorException ( "Executor was closed." );
}
// 创建缓存 key
CacheKey cacheKey = new CacheKey ();
// id、limit、sql等属性,计算 hash值,生成 cacheKey。
cacheKey . update ( ms . getId ());
cacheKey . update ( rowBounds . getOffset ());
cacheKey . update ( rowBounds . getLimit ());
cacheKey . update ( boundSql . getSql ());
// 如果存在参数映射,将参数也加入cacheKey的生成元素中。
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 ;
}
}
4 一级缓存
mybatis 中的缓存是通过 Cache 接口来实现的。它负责存储缓存的 key-value 内容。
一级缓存的生命周期与 sqlSession 相同,即是线程隔离的。它的触发点是在二级缓存之后,也就是说,二级缓存中没有内容时,才会从一级缓存中获取,最后从数据库获取。
在 BaseExecutor 中有成员变量 localCache,这里就保存了一级缓存的内容。
默认的缓存实现类是 PerpetualCache,它通过 HashMap 来存储数据,其数据结构源码如下:
1
2
3
4
5
6
public class PerpetualCache implements Cache {
private final String id ;
private final Map < Object , Object > cache = new HashMap <> ();
}
如果我们调用一个查询语句,触发一级缓存的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class BaseExecutor implements Executor {
@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 ());
// 检查当前 executor 是否关闭。
if ( closed ) {
throw new ExecutorException ( "Executor was closed." );
}
// 非嵌套查询,并且 flushCacheRequired 配置为 true,则需要清空一级缓存。
if ( queryStack == 0 && ms . isFlushCacheRequired ()) {
clearLocalCache ();
}
List < E > list ;
try {
// 查询层次 +1
queryStack ++ ;
// 查询一级缓存
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 -- ;
}
// 其余源码省略...
return list ;
}
}
根据 cacheKey 从一级缓存中获取数据,如果存在,直接返回。如果不存在,则从数据库中查询数据。
5 二级缓存
二级缓存存储在 MappedStatement 的变量 cache 中,也就是与 Mapper 文件的封装类实例生命周期一致,是应用级的。
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CachingExecutor implements Executor {
@Override
public < E > List < E > query ( MappedStatement ms , Object parameterObject , RowBounds rowBounds , ResultHandler resultHandler ) throws SQLException {
// 获取 sql 语句信息,包括占位符、参数等信息。
BoundSql boundSql = ms . getBoundSql ( parameterObject );
// 拼装缓存 key 值
CacheKey key = createCacheKey ( ms , parameterObject , rowBounds , boundSql );
// 执行查询
return query ( ms , parameterObject , rowBounds , resultHandler , key , boundSql );
}
@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 );
// 从二级缓存中获取数据。
@SuppressWarnings ( "unchecked" )
List < E > list = ( List < E > ) tcm . getObject ( cache , key );
if ( list == null ) {
// 二级缓存为空,才会调用 BaseExecutor.query()。
list = delegate . query ( ms , parameterObject , rowBounds , resultHandler , key , boundSql );
tcm . putObject ( cache , key , list ); // issue #578 and #116
}
return list ;
}
}
// 执行查询
return delegate . query ( ms , parameterObject , rowBounds , resultHandler , key , boundSql );
}
}
二级缓存和一级缓存 cacheKey 的生成并无区别。仅仅是存储的位置不同,导致其生命周期不同,这是他们最大的区别。
6 小结
一级缓存和二级缓存只是作用范围、生命周期不同。一级缓存是在sqlSession中,查询时保存到缓存、更新时清除缓存。二级缓存是在 MappedStatement 中,更新时会清除对应id的 MappedStatement 中的二级缓存。
二级缓存可能存在跨 MappedStatement 的情况,所以存在一致性问题,一般不使用。