Mybatis
文章目录
mybatis加载过程
-
Config.xml全局配置文件解析放到configuration对象中,Java中的注解配置及SQL配置文件封装到statement对象中存储在内存,同样由configuration对象来维护
-
映射文件加载:mappedStatement对象解析sql,每个sql对象对应一个statementID,将解析后的sql存储到map中,key就是statementId,value就是解析后的s q l,存储到sqlsource中
-
sqlSource存储sql信息,还提供对sql的解析功能
dynamicsqlSource:${}解析这种动态sql
RawsqlSource:#{}解析这种动态sql
StaticsqlSource:将以上两种sql解析后,存储到sqlSource,通过这个sqlSource可以获取到BoundSql
结合staticSqlSource源码理解会会更容易些
public class StaticSqlSource implements SqlSource {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
-
BoundSql:
String sql;解析后的s q l
List ParameterMappings; 对应传入的参数
sqlNode :树状结构
sqlSource存储sql信息,是通过sqlNode 的集合
Mybatis 一级缓存
mybatis是有一级缓存和二级缓存的
一级缓存
介绍
一级缓存是基于sqlSession的
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);
// 第一次查询
Demo demo1 = demoMapper.findById(1);
// 第二次查询
Demo demo2 = demoMapper.findById(1);
上述代码,两次连续使用同一demoMapper进行查询,第一次查询确实会对数据库进行连接查询,而到第二次的时候,则会去mybatis的一级缓存中去取。也就是第一次查询后,会将查询结果存储到一级缓存,之后则再次查询会在一级缓存中去查找,匹配到则返回,匹配不到再去数据库中查询。
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);
// 第一次查询
Demo demo1 = demoMapper.findById(1);
//更新
Demo demo = new User();
demo.setId(1);
demo.setName("test");
demoMapper.updateDemo(demo);
// 第二次查询
Demo demo2 = demoMapper.findById(1);
上述代码,在第一次查询后,又对数据进行了update,再第二次查询。
过程:
- 第一次查询对数据库进行连接查询,并将查询结果存储到一级缓存。
- 在将数据update,update操作会执行清空一级缓存,所以此时一级缓存中对应的缓存被清空了。
- 在进行第二次查询时,一级缓存中并不会获取到,所以去数据库中查询。
源码分析
我们找到SqlSesion接口.其中看起来对一级缓存有作用的也就是一个clearCache()方法
1.进入clearCache的()方法的实现DefaultSqlSession
源码如下:为了方便观看,我就不截图了,直接粘贴源码
public void clearCache() {
this.executor.clearLocalCache();
}
2.进入到Executor接口
3.进入到Executor中clearLocalCache()方法实现
BaseExecutor类中实现的源码如下
public void clearLocalCache() {
if (!this.closed) {
this.localCache.clear();
this.localOutputParameterCache.clear();
}
}
4.进入到localCache.clear()方法:
PerpetualCache类源码如下
public void clear() {
this.cache.clear();
}
5.cache.clear(),cache对象是什么呢?
看下面的源码,原来执行clear的对象只是一个简单的map的对象。从这里我们就可以知道mybatis的一级缓存是使用Map来存储的。
----------------------------------------------------------------------分割线----------------------------------------------------------------------------
上面介绍的源码一看就是一级缓存的清空。那创建呢?
我们来回顾下上面源码的过程
SqlSession–DefaultSqlSession–Executor–BaseExecutor–PerpetualCache
Executor是什么?执行器,我们知道执行器是用来执行SQL请求的,既然清空缓存都在excutor执行了,那么执行完SQL请求获取到结果存储到一起缓存的操作肯定也是在这里执行的。
1.Executor中的方法,关于创建缓存的方法是createCacheKey()方法
2.去实现类BaseExecutor中看具体的实现
源码如下:
@Override
public <E> List<E> 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);
}
注意到这行代码CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
这里的createCacheKey()方法源码:内容直接写到下面代码的注释了
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
// boundSQL的ID,应该就是那个statementID,类似路径包名+类名+sql这样
cacheKey.update(ms.getId());
// 就是SQL中使用的offset,控制查询范围的那种
cacheKey.update(rowBounds.getOffset());
// SQL中有没有limit限制查询数量
cacheKey.update(rowBounds.getLimit());
// 这里就是具体的SQL语句了
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
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);
}
// 这里value是什么,可以去回顾下加载过程,ParameterMapping存储的是SQL中的参数,所以这里就是存储的参数信息
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
综上:一级缓存就是在这里创建好了。其中存储的是什么,在上面的注释中已经备注好了,下面再罗列下
CacheKey cacheKey = new CacheKey();
// boundSQL的ID,应该就是那个statementID,类似路径包名+类名+sql这样
cacheKey.update(ms.getId());
// 就是SQL中使用的offset,控制查询范围的那种
cacheKey.update(rowBounds.getOffset());
// SQL中有没有limit限制查询数量
cacheKey.update(rowBounds.getLimit());
// 这里就是具体的SQL语句了
cacheKey.update(boundSql.getSql());
// 这里value是什么,可以去回顾下加载过程,ParameterMapping存储的是SQL中的参数,所以这里就是存储的参数信息
cacheKey.update(value);
二级缓存
介绍
二级缓存也是存储查询结果和对应的SQL,查询完放到缓存,再次查询可以从里面去取。
那么它和一级缓存有什么区别呢
一级缓存上面已经介绍了,是基于SQLSession的,即同一个sqlSession.
二级缓存是基于mapper的nameSpace的,也就是一个mapper(也可以是nameSpace相同的多个mapper)的二级缓存,可以有多个sqlSession
并且二级缓存需要我们手动开启
使用
在全局配置文件中加入如下代码
再在需要使用的具体mapper中加入如下代码
mybatis默认使用的实现类是上面源码中追踪到PerpetualCache缓存类,实现了cache接口。所以这里我们也可以自己定义二级缓存的实现类,实现cache接口。
分布式下的二级缓存
mybatis怎么实现分布式缓存呢。按照上面的缓存来操作的话,只是单体缓存。
分布式缓存:多台服务器都可以访问操作缓存区
这里我们可以使用Redis结合mybatis来实现分布式缓存
使用方法如下:
1.pom文件增加依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
2.具体使用的mappper中指定要使用的缓存实现类
<cache type="org.mybatis.caches.redis.RedisCache"></cache>
3.配置redis信息等等
源码分析
我们来看下redisCache实现的二级缓存
1.构造方法
从这里的代码我们可以看到,Redis实现的二级缓存使用的是jedis来进行操作的。
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(),
redisConfig.getDatabase(), redisConfig.getClientName());
}
2.put get方法,从这里我们来看二级缓存的存储结构是什么?
从hset命令,很容易知道使用的是hash结构来存储缓存数据
@Override
public void putObject(final Object key, final Object value) {
execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
return null;
}
});
}
@Override
public Object getObject(final Object key) {
return execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));
}
});
}