流程原理分析系列:
MyBatis原理分析之获取SqlSessionFactory
背景
- 开启了二级缓存
- 查询单个对象
实例代码如下:
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); // 默认是DefaultSqlSession SqlSession openSession = sqlSessionFactory.openSession(); try { // 得到的是一个代理对象 MapperProxy EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class); Employee employee = mapper.getEmpById(1); System.out.println(mapper); System.out.println(employee); } finally { openSession.close(); }
前面我们获取的Mapper接口的代理对象,下面我们分析代理对象查询单个对象的流程。时序图如下:
【1】MapperProxy和MapperMethod
前面我们分析了获取Mapper接口的代理对象,会首先创建MapperProxy。MapperProxy是一个实现了InvocationHandler接口的调用处理程序,当调用Mapper接口的代理对象时,会触发MapperProxy的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //找到缓存的MapperMethod ,如果没有就新实例化一个,这个很重要 final MapperMethod mapperMethod = cachedMapperMethod(method); // args是什么呢,就是触发mapper接口方法时我们传的参数, //其是一个Object数组,里面存放了我们传入的具体值 return mapperMethod.execute(sqlSession, args); }
① MapperMethod
MapperMethod是什么?先看其属性和构造函数。
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } //... }
1.1 SqlCommand是什么?
其是MapperMethod的静态嵌套类,我们看其属性和构造函数。
public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { String statementName = mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null; if (configuration.hasStatement(statementName)) { ms = configuration.getMappedStatement(statementName); } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35 String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); if (configuration.hasStatement(parentStatementName)) { ms = configuration.getMappedStatement(parentStatementName); } } if (ms == null) { if(method.getAnnotation(Flush.class) != null){ name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + statementName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } }
可以看到:
name是MappedStatement 的id,也就是namespace+mapper接口的方法名称
type 为UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH其中之一
也就是SqlCommand 是从MappedStatement 抽离出来的的简单标明某个MappedStatement 是一个什么样的SQL动作。
1.2 MethodSignature是什么?
方法签名,顾名思义维护了方法的诸多特征如返回类型。其是MapperMethod的静态嵌套类,MethodSignature
属性和构造函数如下所示:
public static class MethodSignature { private final boolean returnsMany;//是否多值查询 private final boolean returnsMap;//是否map查询 private final boolean returnsVoid;//是否void查询 private final boolean returnsCursor;//是否游标查询 private final Class<?> returnType; //返回类型 private final String mapKey;//获取mapKey的值 //ResultHandler 参数在参数列表中的位置 private final Integer resultHandlerIndex; //RowBounds 参数在参数列表中的位置 private final Integer rowBoundsIndex; //参数解析器 private final ParamNameResolver paramNameResolver; public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); } else { this.returnType = method.getReturnType(); } // 检测返回值类型是否是 void、集合或数组、Cursor、Map 等 this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray()); this.returnsCursor = Cursor.class.equals(this.returnType); // 解析 @MapKey 注解,获取注解内容 this.mapKey = getMapKey(method); this.returnsMap = (this.mapKey != null); // 获取 RowBounds 参数在参数列表中的位置, //如果参数列表中包含多个 RowBounds 参数,此方法会抛出异常 this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); // 获取 ResultHandler 参数在参数列表中的位置 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); //获取参数解析器,构造函数会解析方法的参数列表 this.paramNameResolver = new ParamNameResolver(configuration, method); } //... }
代码解释如下:
① 获取返回类型
② 判断返回类型是否是 void、集合或数组、Cursor、Map 等
③ 获取mapKey并判断是否返回Map,不存在则为null;
④ 确定RowBounds在参数列表的位置,如果有多个则抛出异常;不存在则为null。
⑤ 确定ResultHandler在参数列表的位置,如果有多个则抛出异常;不存在则为null。
⑥ 实例化参数解析器ParamNameResolver
1.3 ParamNameResolver是什么?
顾名思义,参数名称解析器。在实例化方法签名MethodSignature会实例化ParamNameResolver。我们看下属性和构造函数,构造方法最重要的功能就是实例化了SortedMap<Integer, String> names,该SortedMap维护了方法的参数下标与参数名称的映射关系。
public class ParamNameResolver { private static final String GENERIC_NAME_PREFIX = "param"; private static final String PARAMETER_CLASS = "java.lang.reflect.Parameter"; private static Method GET_NAME = null; private static Method GET_PARAMS = null; static { try { Class<?> paramClass = Resources.classForName(PARAMETER_CLASS); GET_NAME = paramClass.getMethod("getName"); GET_PARAMS = Method.class.getMethod("getParameters"); } catch (Exception e) { // ignore } } //维护了方法的参数 索引下标--参数名称 private final SortedMap<Integer, String> names; private boolean hasParamAnnotation; public ParamNameResolver(Configuration config, Method method) { final Class<?>[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap<Integer, String> map = new TreeMap<Integer, String>(); int paramCount = paramAnnotations.length; // get names from @Param annotations for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) { hasParamAnnotation = true; name = ((Param) annotation).value(); break; } } if (name == null) { // @Param was not specified. useActualParamName 属性在新版本mybatis中默认为TRUE! if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex); } if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } } map.put(paramIndex, name); } names = Collections.unmodifiableSortedMap(map); } //... }
这里先看下常量成员SortedMap<Integer, String> names
//其实一个UnmodifiableSortedMap类型,排序且不可修改 private final SortedMap<Integer, String> names
- key是索引index,值是参数的名称;
- 如果参数被@Param注解重命名,则取其注解值作为name值;
- 如果么有使用@Params注解,则取其参数索引作为name值;
- 注意,参数索引不计算RowBounds、ResultHandler类型
aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}; aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}; //跳过RowBounds rb aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
也就是说names里面保存的是第几个参数对应的参数名称是什么。
然后我们解释下构造函数的内容
判断当前参数类型是否RowBound或ResultHandler,如果是则进行下次循环;
如果有@Param注解,则@Param注解的值作为name的值;
如果configuration中useActualParamName为true,则根据参数索引解析参数name值,诸如arg0、arg1…
获取当前map的大小作为name的值
SortedMap<Integer, String> names
可能如下:
//@Param("idxxx") id {0=idxx} //configuration中useActualParamName为true {0=arg0} //前两种都不存在 {0=0}
如下图是一个names实例:
② Object execute(SqlSession sqlSession, Object[] args)
MapperMethod
的execute
方法是核心入口方法,调用MapperProxy的invoke方法中就会触发mapperMethod.execute(sqlSession, args);
。execute源码如下所示:
pub 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: 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; }
代码解释如下:
① 根据command.getType也就SQL动作类型,判断走哪个分支;
② method.convertArgsToSqlCommandParam(args)解析参数,可能返回一个单独值或者map
③ 根据②返回的参数进行不同分支的CRUD,拿到结果result;
④ 判断是否抛出异常或者返回result。
③ 参数解析convertArgsToSqlCommandParam
这里我们看一下method.convertArgsToSqlCommandParam(args)
是如何解析参数的。该方法内部调用了paramNameResolver.getNamedParams(args)
。
这里names
是{"0":"0"}
,args
是[1]
,args是传递的参数值,是一个Object[]
。
//也就是获取Object[] args中每一个值对应的参数key是什么 public Object getNamedParams(Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; } else { final Map<String, Object> param = new ParamMap<Object>(); int i = 0; for (Map.Entry<Integer, String> entry : names.entrySet()) { param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); // ensure not to overwrite parameter named with @Param if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; }
代码解释如下:
- ① 如果
args
为null
或者names
为空,直接返回null
; - ② 如果没有使用
@Param
注解并且names.size()==1
,直接返回args[names.firstKey()]
;
③ 遍历names.entrySet
,向里面放入两种类型键值对:
param.put(entry.getValue(), args[entry.getKey()]); param.put(genericParamName, args[entry.getKey()]); 这里如{"0":"1","param1":"1"}
【2】DefaultSqlSession
回顾一下DefaultSqlSession的核心成员与构造方法如下,其核心成员 configuration 与 exceutor均是private final修饰也就是赋值后不可再更改。在实例化DefaultSqlSession时,已经为其指定了执行器Executor ,这是进行DML操作的基础组件。
private final Configuration configuration; private final Executor executor; private final boolean autoCommit; private boolean dirty; private List<Cursor<?>> cursorList; public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autoCommit = autoCommit; }
① selectOne
方法源码如下:
@Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>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; } }
这里statement就是ms的id,就是namespace+方法名称(insert|update|select|delete的id),如com.mybatis.dao.EmployeeMapper.getEmpById。
parameter就是前面解析的参数值或者参数-值Map集合。代码解释如下:
- ① 调用selectList获取结果list;
- ② 如果①中获取的结果list只有一个值,直接返回;
- ③ 如果①中获取的结果list.size()>1,抛出异常;
- ④ 否则返回null。
② selectList
相关代码如下:
@Override public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); 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(); } }
代码解释如下:
根据statement从config实例的final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>中获取MappedStatement 对象
对参数值(parameter对象)进行集合包装后,使用executor进行查询wrapCollection方法如下所示:
private Object wrapCollection(final Object object) { if (object instanceof Collection) { StrictMap<Object> map = new StrictMap<Object>(); map.put("collection", object); if (object instanceof List) { map.put("list", object); } return map; } else if (object != null && object.getClass().isArray()) { StrictMap<Object> map = new StrictMap<Object>(); map.put("array", object); return map; } return object; }
代码解释如下:
① 如果是Collection类型
② 不是List类型,则往StrictMap中放入 map.put("collection", object);,返回map
③ 是List类型,则在②基础上额外放入map.put("list", object);然后返回map
④ 如果是Array类型,则放入map.put("array", object);,返回map
⑤ 返回object
【3】CachingExecutor
CachingExecutor属性和构造函数如下:
public class CachingExecutor implements Executor { private Executor delegate; private TransactionalCacheManager tcm = new TransactionalCacheManager(); public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } //... }
TransactionalCacheManager属性如下(无有参构造方法)
public class TransactionalCacheManager { private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>(); //... }
TransactionalCache属性和构造函数如下:
public class TransactionalCache implements Cache { private static final Log log = LogFactory.getLog(TransactionalCache.class); //实际缓存对象 private Cache delegate; private boolean clearOnCommit; //缓存key---查询结果 private Map<Object, Object> entriesToAddOnCommit; //没有对应value的缓存key private Set<Object> entriesMissedInCache; public TransactionalCache(Cache delegate) { this.delegate = delegate; this.clearOnCommit = false; this.entriesToAddOnCommit = new HashMap<Object, Object>(); this.entriesMissedInCache = new HashSet<Object>(); } //... }
通过上面代码可以看到:
每一个CachingExecutor 有一个对应的TransactionalCacheManager(事务缓存管理器)。
每一个TransactionalCacheManager维护了一个Map<Cache, TransactionalCache> transactionalCaches。
回顾一下,每一个SqlSession有一个成员Executor。mapper接口的代理对象是通过SqlSession获取的,代理对象有指向sqlsession实例的引用。所以在一次会话间,如果对不同namespace进行了CRUD操作,那么所遇到的Cache可能有多个。故而这里transactionalCaches 是一个new HashMap<Cache, TransactionalCache>();类型,也标明了二级缓存的级别是namespace。言归正传,我们继续看查询过程。
@Override public <E> List<E> 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); }
① 获取BoundSql
MappedStatement.getBoundSql方法源码如下:
public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } // check for nested result maps in parameter mappings (issue #30) for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; }
代码解释如下:
- ① 根据
sqlSource
和参数值对象parameterObject
获取BoundSql
。每一个MappedStatement都有一个SqlSource实例,sqlsource
实例如下: - ② 如果①中获取的
boundSql
对象的parameterMappings
为空,则新建一个BoundSql
实例对象; - ③ 对
boundSql
对象的parameterMappings
进行遍历循环,判断在ParameterMapping
中是否有嵌套结果映射。
② 获取CacheKey
装饰者模式的应用,CachingExecutor内部有一个被装饰者Executor delegate
。
@Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); }
可以看到这里CachingExecutor
交给了其装饰的delegate
(这里是SimpleExecutor
)来处理。SimpleExecutor
将会调用父类BaseExecutor
的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(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); 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; }
可以看到这里CachingExecutor
交给了其装饰的delegate
(这里是SimpleExecutor
)来处理。SimpleExecutor
将会调用父类BaseExecutor
的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(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); 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; }
CacheKey是什么?
每个MappedStatement都可能对应一个CacheKey,唯一标识在Cache中的存储。
ppublic class CacheKey implements Cloneable, Serializable { private static final long serialVersionUID = 1146682552656046210L; public static final CacheKey NULL_CACHE_KEY = new NullCacheKey(); private static final int DEFAULT_MULTIPLYER = 37; private static final int DEFAULT_HASHCODE = 17; private int multiplier; 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>(); } //... }
private void doUpdate(Object object) { int baseHashCode = object == null ? 1 : object.hashCode(); //默认值 0 count++; checksum += baseHashCode; baseHashCode *= count; //默认值37 hashcode默认值17 hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); }
算法逻辑解释如下:
① 计算每个入参的hashCode作为baseHashCode ;
② 计算次数或者处理次数count +1;
③ checksum 记录目前为止所有入参的hashCode和;
④ hashcode = multiplier * hashcode + baseHashCode*count;
⑤将当前object放入updateList中这里得到的CacheKey如下所示:
-272712784:4091614186:com.mybatis.dao.EmployeeMapper.getEmpById:0:2147483647 :select id,last_name lastName,email,gender from tbl_employee where id = ? :1:development
hashcode:checksum:ms.getId():rowBounds.getOffset():rowBounds.getLimit():boundSql.getSql():value:environment.getId()
③ query查询数据
CachingExecutor的query方法如下:
//参数parameterObject指的是参数值对象 @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
代码解释如下:
① 获取当前MappedStatementd对应的二级缓存对象Cache;
② 如果Cache不为空:
③ 判断是否需要清空缓存,需要则清空;
④ 如果isUseCache为true且resultHandler为空则执行5678,否则直接执行9
⑤ 确保没有out类型参数-针对StatementType.CALLABLE而言,只适用于ParameterMode.IN;
⑥ 从缓存中获取结果list;
⑦ 如果结果list不为空则直接返回;
⑧ 如果list为空则进行数据库查询然后将查询结果放入缓存Cache中;
⑨ 如果Cache为空,则直接调用数据库查询结果
【4】BaseExecutor
CachingExecutor将数据库查询操作交给了SimpleExecutor(CachingExecutor对SimpleExecutor进行了装饰)。而SimpleExecutor继承自抽象父类BaseExecutor,并没有覆盖父类的 query方法,故而这里直接走到BaseExecutor的query方法。
① query
@SuppressWarnings("unchecked") @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()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { 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--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
代码解释如下:
① 如果需要,则清理本地缓存localCache和localOutputParameterCache-这是CALLABLE类型中参数为out类型;
② 如果resultHandler为null,则尝试从本地缓存中根据缓存key获取结果list:
③ 如果list不为null,则尝试处理CALLABLE类型中参数为out类型(如果当前是CALLABLE且参数为out`类型);
④ 如果list为null,则进行数据库查询;
⑤ 如果deferredLoads不为空,则遍历循环进行延迟加载处理。处理完后,清空deferredLoads
⑥ 如果localCacheScope==STATEMENT,则清空一级缓存;
② queryFromDatabase
查询方法如下所示,这里参数中key是缓存key,parameter是参数值对象(可能是一个值,也可能是一个map等)。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> 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; }
代码解释如下:
① localCache本地一级缓存中放入当前缓存key与占位对象;
② 执行数据库查询;
③ 从localCache移除key,然后放入key-list
④ 如果当前语句是CALLABLE,则将key-parameter放入localOutputParameterCache;
这里需要注意的是,localCache默认是PerpetualCache实例,其内部使用private Map<Object, Object> cache = new HashMap<>();维护缓存数据。而PerpetualCache实例是BaseExecutor实例的一个普通成员,并没有使用ThreadLocal维护。所以如果多个线程使用同一个sqlsession实例进行数据库操作的时候 ,可能出现并发问题,sqlsession并非线程安全!
如下图所示,在 localCache.putObject(key, list);处中断,线程1读取数据库结果为2条,线程2读取了3条,那么此时无论谁先更新缓存,对其都可能造成“幻读”错觉!如何解决sqlsession线程不安全问题呢?最好的方案是保证每一个事务或线程拥有独属的sqlsession!
【5】SimpleExecutor
① doQuery
前面的query可以理解为前置处理、后置处理,这里doQuery是解析参数、查询数据并处理返回结果的核心方法。
@Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
代码解释如下:
① 获取全局的configuration实例;
② 获取RoutingStatementHandler实例,在此过程中还会创建ParameterHandler和ResultSetHandler实例。interceptorChain.pluginAll对StatementHandler实例进行代理。
③ 获取预编译处理语句并进行参数解析;
④ 使用StatementHandler 进行查询;
⑤ closeStatement关闭连接等,然后返回结果。
MyBatis提供了一种插件机制,具体来说就是四大对象Executor、StatementHandler、ParameterHandler、ResultSetHandler的每个对象在创建时,都会执行interceptorChain.pluginAll(),会经过每个插件对象的 plugin()方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行,因为要执行四大对象的方法需要经过代理 。
接下来我们详细分析②③④的具体过程。
② configuration.newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
代码解释如下:
- ① 先创建RoutingStatementHandler实例;
- ② 对statementHandler 实例进行拦截器层层代理;
RoutingStatementHandler
RoutingStatementHandler属性和构造函数。
public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } //... }
上面可以看到,在构造函数中先创建了parameterHandler
实例,然后根据parameterHandler
实例创建了resultSetHandler
实例。
newParameterHandler
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }
代码解释如下:
- ① 创建ParameterHandler,默认是DefaultParameterHandler实例;
- ②
interceptorChain.pluginAll
进行层层插件代理,返回一个代理对象。