在 MyBatis 中,引入缓存的目的是为提高查询效率,降低数据库压力。既然 MyBatis

引入了缓存,那么缓存中的key和value的值分别是什么?

可能很容易能回答出value的内容,不就是SQL的查询结果吗。那key是什么呢?是字符串,还是其他什么对象?

如果是字符串的话,那么大家首先能想到的是用SQL语句作为key。但这是不对的,比如:

1
SELECT * FROM author where id > ?

id > 1 和 id > 10 查出来的结果可能是不同的,所以我们不能简单的使用SQL语句作为key

  • 运行时参数将会影响查询结果,因此我们的key应该涵盖运行时参数。
  • 除此之外,进行分页查询,查询结果也会不同,因此key也应该涵盖分页参数。

综上,我们不能使用简单的 SQL 语句作为 key。应该考虑使用一种复合对象,能涵盖

可影响查询结果的因子。在 MyBatis 中,这种复合对象就是 CacheKey。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CacheKey implements Cloneable, Serializable {
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
// 乘子,默认为 37
private final int multiplier;
// CacheKey 的 hashCode,综合了各种影响因子
private int hashcode;
// 校验和
private long checksum;
// 影响因子个数
private int count;
// 影响因子集合
private List<Object> updateList;

public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<Object>();
}
}

除了 multiplier 是恒定不变的 ,其他变量将在更新操作中被修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** 每当执行更新操作时,表示有新的影响因子参与计算 */
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
// 自增 count
count++;
// 计算校验和
checksum += baseHashCode;
// 更新 baseHashCode
baseHashCode *= count;
// 计算 hashCode
hashcode = multiplier * hashcode + baseHashCode;
// 保存影响因子
updateList.add(object);
}

当不断有新的影响因子参与计算时,hashcode 和 checksum 将会变得愈发复杂和随机。

这样可降低冲突率,使 CacheKey 可在缓存中更均匀的分布。CacheKey 最终要作为键存入HashMap,因此它需要覆盖 equals 和 hashCode 方法

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
public boolean equals(Object object) {
// 检测是否为同一个对象
if (this == object) {
return true;
}
// 检测 object 是否为 CacheKey
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
// 检测 hashCode 是否相等
if (hashcode != cacheKey.hashcode) {
return false;
}
// 检测校验和是否相同
if (checksum != cacheKey.checksum) {
return false;
}
// 检测 coutn 是否相同
if (count != cacheKey.count) {
return false;
}

// 如果上面的检测都通过了,下面分别对每个影响因子进行比较
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;
}

public int hashCode() {
// 返回 hashcode 变量
return hashcode;
}
  • equals 方法的检测逻辑比较严格,对CacheKey中多个成员变量进行了检测,已保证两者相等。
  • hashCode 方法比较简单,返回 hashcode 变量即可。