关于mybatis的一二级缓存,一级缓存,默认开启的,二级缓存需要配置,使用比较简单,不清楚的可以看我之前的博文mybatis一级二级缓存
前面我们就提到了mybatis的一级缓存是相对于SqlSession定义,二级缓存相对于mapper来定义的。
这里我们一步一步的来剖析
一般mybatis调用数据库代码,大致如下,但是现在很多框架已经做好了封装,比如mybatis-plus直接封装好了,不需要我们操作SqlSession。
这里我只依赖了mybatis和mybatis-spring,不使用其他框架。
mSession = SqlSessionUtils.getSqlSession(sqlSessionFactory);
ImsUserMapper mapper = mSession.getMapper(ImsUserMapper.class);
imsUser = mapper.findById("1");
imsUser2 = mapper.findById("1");
mSession.commit();
我们先从SqlSessionUtils的getSqlSession看起,它需要传递sqlSessionFactory,这个sqlSessionFactory是通过spring注解
@Autowired
SqlSessionFactory sqlSessionFactory;
获取的。
getSqlSession代码如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory) {
ExecutorType executorType = sessionFactory.getConfiguration().getDefaultExecutorType();
return getSqlSession(sessionFactory, executorType, null);
}
这里调用了DefaultSqlSessionFactory的getConfiguration先获取了Configuration对象,DefaultSqlSessionFactory为SqlSessionFactory 的实现类;然后获取了ExecutorType,这里Configuration其实保存了很多数据,调试一下即可看到。
我们继续跟进getSqlSession方法,看看怎么获取的SqlSession
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
//在这里通过sessionFactory工厂得到了session
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
openSession里面又干了什么?我们继续跟
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
再跟进
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
//原来是在这里new的一个DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
我们可以看到new DefaultSqlSession时传递了configuration,这里就得到了SqlSession
获取mapper利于的是SqlSession的getMapper方法看起,从上面可以看到是DefaultSqlSession实现了SqlSession接口,我们打开DefaultSqlSession的getMapper方法
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
这里可以看到从configuration的getMapper获取的,继续跟进
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
调用了mapperRegistry.getMapper,mapperRegistry是configuration的一个对象。继续跟进
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
从这里可以看到:
(1)先从knownMappers的map集合中获取MapperProxyFactory对象,knownMappers怎么有MapperProxyFactory对象呢?这个是从启动时,扫描出来的,有兴趣的可以看看启动时,扫描mapper。
(2)通过mapperProxyFactory.newInstance(sqlSession);利用了jdk动态代理生成了mapper的代理类MapperProxy
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
至此我们获得了mapper的代理类MapperProxy,如果有对jdk动态代理不熟悉的,建议先看一下我之前的博客浅谈java动态代理
我这里是一个findById的方法,我们知道jdk动态代理,执行方法时会调用它的invoke方法来调用,这里我们打开MapperProxy的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
从这里我们可以看到执行了cachedMapperMethod方法
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
(1)从这里我们会看到,先从methodCache中获取mapperMethod ,如果缓存为null,则new一个MapperMethod,然后放入methodCache缓存。
(2)存储时的MapperMethod的持有sqlSession.getConfiguration()的Configuration对象,只要sqlSession不被close,或者销毁,获取时,就可以得到缓存的mapperMethod,具体细节如果想深究的,可继续跟进查看。
(3)当我们第二次调用这个方法,就获取了缓存里面的数据
好了,到这里我们就大概知道缓存是怎么放进去的,然后怎么获取的。
想要了解二级缓存,我们得先了解mybatis怎么解析mapper的xml文件,从源码可以看到,mybatis使用了XMLMapperBuilder来解析mapper.xml。
我们知道要想使用二级缓存,
(1)setting文件里要设置全局的cacheEnabled为true,新版本默认是true
(2)在mapper.xml里添加标签。
这个cache节点的生成就在XMLMapperBuilder里面,我们看一下源码
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
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);
}
}
cacheElement方法源码如下:
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
我们看到最后调用了一个useNewCache的方法,我们继续跟进这个useNewCache方法。
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();
//生成的cache添加到了configuration
configuration.addCache(cache);
currentCache = cache;
return cache;
}
从这里我们可以看到使用了构造者模式生成了cache,并将cache放到了configuration里了。
看一下addCache方法
public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}
cache保存在caches的map集合里,这个后面会使用。
这里我们以举例的方法findById开始说起,我们知道方法执行时,使用了jdk动态代理调用了invoke方法。从上文中1.3可以看到invoke方法最后执行了下面这一句
return mapperMethod.execute(sqlSession, args);
我们继续跟进execute方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT://我们调用的是select方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
我们调用的是select方法,找到case下的select,我们实际执行的调用方法的sqlSession的selectOne方法。
小插曲:
这里就是我们经常所说的sqlSession发送sql有两种方式,
(1)通过通过getMapper先获取mapper,通过mapper发送;
(2)通过sqlSession的selectOne发送;
它两一种使用了动态代理,一种未使用,最终都是执行了selectOne
好了,我们返回主题,继续跟进selectOne方法
@Override
public T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
selectOne中执行了selectList获取结果,继续跟进
@Override
public List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1、先从configuration获取了MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//2、调用了executor的query方法
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
selectList又调用了executor的query方法获取结果,继续跟进
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
//这里进行了cache 是否为null的判断
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List list = (List) tcm.getObject(cache, key);//这里获取缓存的结果对象
if (list == null) {//如果缓存为null,就执行delegate的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);
}
追踪到这里可以看到query方法,其实是进行了cache 判断,如果cache 存在就进行二级缓存相关的操作。
(1)tcm.getObjec先获取缓存结果
(2)如果缓存不存在,就去数据库执行sql获取结果,然后放入cache中。
(3)如果存在,直接返回缓存结果list
这里有人就要问了你怎么知道这个cache就是二级缓存的要使用的cache?
这个我们就要往前回一点。
(1)Cache cache = ms.getCache();这个cache 从参数ms获取的
(2)回到selectList方法中,可知道ms是从configuration获取的。
MappedStatement ms = configuration.getMappedStatement(statement);
(3)打开这个MappedStatement的getCache方法
public Cache getCache(String id) {
return caches.get(id);
}
从caches里面通过id的key获取。
这个caches从哪里来的呢?
这个caches就是从2.1中put进去的。假如我们没有在mapper.xml里面没有开启
好了就到这里结束了。我个人理解有限,如果发现有误或者有不明白的,可以评论留言我。谢谢。