前言
本文章将介绍中的一级缓存和二级缓存。主要是围绕着下列问题去展开
一级缓存
会在会话对象对象中建议一个简单的缓存,将查询到的结果缓存起来,当下次查询的时候,会判断有没有一个完全一样的查询,如果有直接从缓存中返回。否则查询数据库并存入缓存,最后返回给用户。
这样做主要是避免我们在短时间内,反复地执行相同的查询语句,而得到的结果也是相同的。如果不使用缓存会造成很大的资源浪费。
一级缓存的原理及源码分析
: 的:分页设置,默认为0,从分页对象中获取limit:分页最大页数,从分页对象中获取:查询的sql语句,从对象中获取value:sql的参数,中获取env: 当前的环境,如果.xml有配置mybatis一级缓存和二级缓存, 也会设置到key中
缓存相关的部分源码展示:
创建缓存KEY的源码
CacheKey cacheKey = new CacheKey();
//MappedStatement 的 id 。id就是Sql语句的所在位置包名+类名+ SQL名称
cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
//具体的SQL语句
cacheKey.update(boundSql.getSql());
//后面是update 了 sql中带的参数
cacheKey.update(value);
//...省略一千万行代码
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
部分query源码:
@Override
public List query(MappedStatement ms, Object parameter,RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
//创建缓存
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException{
//... 省略一千万行代码
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
//处理存储过程的作用
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//如果查不到的话,就从数据库查 queryFromDatabase()
list = queryFromDatabase(ms, parameter,rowBounds,resultHandler,key,boundSql);
}
//... 省略一千万行代码
}
/**
* queryFromDatabase中,会对localcache进行写入。
* localcache对象的put方法最终交给Map进行存放
*/
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//执行查询
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;
}
二级缓存
二级缓存的原理和一级缓存的原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取;
开启默认二级缓存的配置
开启的方式有以下三种:
1. 在全局配置文件.xml添加以下配置开启
2. 在.xml中添加配置开启
3. 在类中添加注解开启
//使用二级缓存,等用于属性
@CacheNamespace
public interface UserMapper{
}
使用Redis实现二级缓存
使用默认的二级缓存实现有哪些弊端?使用缓存中间件实现二级缓存解决了哪些问题?
使用默认的二级缓存实现,只能在单机系统中使用,不能实现分布式缓存。
引入缓存中间件redis作为二级缓存的存储介质,可以实现多机分布式缓存数据存储,对缓存数据进行集中管理。
org.mybatis.caches
mybatis-redis
1.0.0-beta2
默认情况下,如果项目已经有使用了redis,且没有其他特殊配置,不配置redis.也是可以的。
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
xml中配置
注解中配置
@CacheNamespace(implementation=RedisCache.class)
public interface UserMapper{
}
源码分析实现了的Cache接口内部使用jedis封装了对redis的CURD操作使用了redis的Hash数据结构,系列化后存储数据在启动的时候,由的创建
部分核心源码:
//实现了Mybatis的Cache接口
public final class RedisCache implements Cache {
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require anID");
}
this.id = id;
//调用RedisConfigurationBuilder创建RedisConfig
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance()
//RedisConfig核心方法,这个是加载解析redis配置文件的
.parseConfiguration();
pool = new JedisPool(redisConfig,redisConfig.getHost(),redisConfig.getPort(),redisConfig.getConnectionTimeout(),redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName());
}
...
}
/**
* 通过类加载器加载redis配置资源,
*/
public RedisConfig parseConfiguration(ClassLoader classLoader) {
Properties config = new Properties();
InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename);
if (input != null) {
try {
//加载配置信息到Properties容器中
config.load(input);
}catch (IOException e) {
throw new RuntimeException( "An error occurred while reading classpath property '" + redisPropertiesFilename + "', see nested exceptions", e);
} finally {
try {
input.close();
} catch (IOException e) {
// close quietly
}
}
}
//redisConfig继承了 JedisPoolConfig
RedisConfig jedisConfig = new RedisConfig();
//开始设置redis配置
setConfigProperties(config, jedisConfig);
return jedisConfig;
}
结语
实际情况中,我个人是比较少直接用到的二级缓存的。都是自己使用redis缓存在业务上做单独的缓存处理(可能是我项目经验不够吧[奸笑])。但是总体来说,其实也是根据系统开发者的想法+业务本身的各种因素去决定的。合适才是最重要的。
前言中的问题,不知道大家是否已经能知道心里的答案呢?可以评论中写出来巩固自己的理解mybatis一级缓存和二级缓存,也能帮助其他小伙伴哦[憨笑]
[玫瑰][玫瑰]你的“关注”“收藏”是我最大的动力。非常感谢。[玫瑰][玫瑰]