Mybatis中获取获取代理对象的之后的执行操作
之前已经说了Mybatis中mapper是怎么和XMl关联起来的已经分析了,Mybaits从XML是怎么和Mapper对应上来的。这里从宏观上介绍一下是怎么执行的。之后会在具体的博客里面介绍。
1. 几个重要的步骤
-
调用方法的时候调用
MapperProxy
的invoke
方法,并且构建MapperMethodInvoker
调用invoke
方法,然后会调用MapperMethod
方法的execute
方法。 -
组装参数,将方法的参数组成Map,Map的key是String,Value是具体的参数值(ParamNameResolver)
-
调用sqlSession的select方法
-
通过StatementId(全限定类名+方法名)从Configuration里面解析
MappedStatement
从下面开始,都是在Executor里面运行的。
-
通过指定器(Executor)执行
query
方法,在这里面构建查询所需要的一切操作。并且执行查询操作,将查询的结果封装实体类,返回-
构建BoundSql对象(通过参数和原始的sql来解析动态sql,生成用于PrepareStatement里面的sql)。
select * from t_class where id = ? and name in ? 复制代码
-
构建
CacheKey
(用作缓存,通过sql,同样的参数,同样的offset或者limit) -
先从二级缓存中查找(如果指定开启)
-
再从一级缓存中查找(这肯定是开启的)
-
创建
StatementHandler
在创建它的时候。会创建ParameterHandler
和ResultSetHandler
,并且调用插件方法包装。 -
调用
StatementHandler
的prepare
方法- 将之前
BoundSql
对象解析好sql设置给Connection(connection.prepareStatement(sql))
- 给查询设置设置超时时间和
fetchSize
- 调用
ParameterHandler
来给设置此次查询的参数。 - 执行查询。
- 调用
ResultSetHandler
来处理结果映射
- 将之前
-
返回结果,并且将结果放在一级缓存里面。
-
如果条件满足,也放在二级缓存里面。
那么下面就简单的来看一下大体的流程吧。之后会围绕具体的方面来分析,流程图就不画了,上面的文字写的也很清楚了。
测试案例@BeforeAll static void setup() throws Exception { //这里只是为了测试方便,运行了测试的脚本。这里的dataSource和MapperConfig里面的没有关系。 createStudentDataSource(); //这就是获取连接,执行脚本。 final String resource = "org/apache/ibatis/builder/MapperConfig.xml"; final Reader reader = Resources.getResourceAsReader(resource); // 加载配置文件,解析mapper,mapper里面的每个方法都对应一个MapperStatement。大体就是这样,但是具体的实现就复杂了,比如动态sql,mybatis的配置信息。 //所有的信息都关联到 SqlSessionFactoryBuilder里面的Configuration实体类。包括mapper,setting,还有代理对象的创建 sqlMapper = new SqlSessionFactoryBuilder().build(reader); } @Test public void testShouldSelectClassUsingMapperClass(){ try( SqlSession session = sqlMapper.openSession() ){ ClassMapper mapper = session.getMapper(ClassMapper.class); long start = System.currentTimeMillis(); System.out.println(mapper.listClassOwnerStudentByClassId(1,new String[]{"狗蛋"})); System.out.println("start:" + (System.currentTimeMillis()-start)); long start1 = System.currentTimeMillis(); System.out.println(mapper.listClassOwnerStudentByClassId(2,new String[]{"狗蛋"})); System.out.println("start1:" + (System.currentTimeMillis()-start1)); long start2 = System.currentTimeMillis(); System.out.println(mapper.listClassOwnerStudentByClassId(1,new String[]{"狗蛋"})); System.out.println("start2:" + (System.currentTimeMillis()-start2)); session.commit(); } } 复制代码
-
调用MethodProxy,调用Invoke方法,并且组装参数
// MapperProxy的Invoke方法,这里会构建MapperMethodInvoker大多数的是PlainMethodInvoker。但是对于default修饰的方法
//是 DefaultMethodInvoker,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {//如果是object类里面的方法,直接调用就好了
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//MapperMethod,通过之前构建的时候确认的类型来判断,这里的模式美其名曰,策略模式
// 这里我用SELECT方法举例,
// 在这个分支里面会根据返回值的类型不同,来约定返回方法分支。
// 下面的代码是按照method.returnsMany()来的。
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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
}
复制代码
组装参数,将方法的参数组成Map,Map的key是String,Value是具体的参数值(ParamNameResolver)
在组装MapperMethod的时候会构建MethodSignature
,这里面会设置ParamNameResolver
,在从上面的代码往下走的时候,会通过ParamNameResolver
来解析参数,即就是组装成map。
// ParamNameResolver中的getNamedParams方法
// 这里的方法能解释在Mapper里面可以用Mybatis提供的默认参数。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) { //这里就可以看到,会给参数设置一个默认的值,如果不设置的话,并且还会生成按照下标会同时会生成GENERIC_NAME_PREFIX前缀,同时也会放在map里面返回
Object value = args[names.firstKey()]; //这里如果只有一个参数,并且它还没有标注param注解,下面的这个方法,就很简单,如果是collection或者array的话,存放默认值,如果不是,就把原来的参数值直接返回
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
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 + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
复制代码
通过StatementId(全限定类名+方法名)从Configuration里面解析MappedStatement
// DefaultSqlSession中的selectList。
// 从configuration的MapperStatement通过StatementId获取MapperStatement。
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try { //拿到mapperStatement,这个是在xml中解析mapper文件的时候解析出来的
MappedStatement ms = configuration.getMappedStatement(statement);
// 查询交给Executor来执行。
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);// 这里就很离谱,居然还再次包装了一下下。很离谱,对应Collection或者Array
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
复制代码
构建BoundSql对象(通过参数和原始的sql来解析动态sql,生成用于PrepareStatement里面的sql)。
如果开启了二级缓存DefaultSqlSession
中的Executor
是CachingExecutor
,如果没有开启就是直接是SimpleExecutor
// ExecutorType有三种,SIMPLE,REUSE,BATCH,一般来说,都是simple。并且它默认的执行器的类型就是simple
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果开启了缓存就用CachingExecutor来包装之前搞好的Executor,
// CachingExecutor里面有一个TransactionalCacheManager,这是mybatis实现二级缓存的重点。
// cacheEnabled这个标识位。就是xml中Setting设置的。,默认是开启,如果开启,就将executor用CachingExecutor包装。
// 在说一下,executorType默认是SIMPLE,并且这个也是可以通过xml中setting的defaultExecutorType来设置的。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//在interceptorChain应用Executor。
//waring 这里也很重要,这是是实现mybatis拦截器的主要实现。主要看这个方法,Interceptor类里面的wrap方法
// 其实这里就是生成了代理对象,调用 Plugin.wrap(target, this);来生成代理对象,在这里会解析interceptor的上面的注解,组成map,key是接口的class。
// value是set,里面存放的需要拦截的方法。如果需要生成代理对象就生成代理对象,InvocationHandler是Plugin。 具体的还得看看方法里面是怎么写的。
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
复制代码
通过executor来执行方法,在这个方法里面会通过参数来解析动态sql。并且将sql中参数的位置变为?
占位符。
//MappedStatement的 getBoundSql方法,在这里会将之前搞好的参数(也就是那个Map)传递进来并且根据这些参数,来解析动态sql。
//动态sql的具体实现是 SqlNode。接口
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;
}
复制代码
构建CacheKey
(用作缓存,通过sql,同样的参数,同样的offset或者limit)
构建缓存key,构建这样的key的要求可多了,从代码就可以看出,一个cachekey方方面面那是得完全一样的呀。包括,参数,原始sql,包括sql参数的位置。我意思是比如有这样的一个sql
select * from t_class where a = 1 and b = 2
select * from t_class where a = 2 and b = 1
复制代码
这两sql对于我们来说,结果是一样的,但对于Mybatis来说,这可不一样,不能共用一个CacheKey。
话说回来,这确实的一模一样,但凡要是有一点不一样,两次查询,只因为那一点不一样,出来的结果确实一样,那不离谱了吗?
// 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 这个parameterMappings,按照下标和?对应。下标为0的,表示为sql中第一个问号。
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);
}
} //上面的都是通过propertyName来获取value。
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
复制代码
先从二级缓存中查找(如果指定开启),再从一级查询,并且将结果放在二级和一级缓存中去。
二级缓存相关代码
// CachingExecutor中的query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 这个缓存是在mapper.xml中配置的。在解析xml文件的时候配置的。
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
// MapperStatement如果要配置缓存,并且resultHandler为null。就去查二级缓存。
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//从二级缓存中查询,如果没有再从一级缓存里面去查,这里的delegate就是BaseExecutor
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//查到的结果放在二级缓存中
tcm.putObject(cache, key, list);
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
复制代码
一级缓存相关代码
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) {//lcnote 这个deferredLoads是干嘛的,
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
//如果缓存缓存的作用域是STATEMENT,在查询结束之后会清楚缓存,这肯定是线程不安全的。有问题,如果两个线程同时操作,一个不需要清楚缓存
// 一个需要清楚缓存,那不就得么的了。出现问题了?
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 在一级缓存中存放执行占位符,
//为什么要这样做呢?如果一个
// 如果先一个查询,后一个再来,正好中间有一个key的话。就拿到脏的数据了。也没看到他对这个数据有什么特殊的处理呀
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 查询数据库
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除掉之前用于占位的key
// 这里的remove我不是很理解,与其说是占位,name在这里直接put覆盖就好了,这里这样做怕是为了防止doQuery发生异常,导致这个缓存是错误的,所以, 这里必须有finally,这都是细节细节
localCache.removeObject(key);
}
localCache.putObject(key, list);//放在一级缓存中。
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
复制代码
创建StatementHandler
在创建它的时候。会创建ParameterHandler
和ResultSetHandler
,并且调用插件方法包装
创建StatementHandler,并且用interceptor包装他,关于interceptor在前面的博客中已经介绍了。
//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;
}
复制代码
创建parameterHandler和resultSetHandler并且interceptor包装
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
复制代码
得到Connection并且设置sql,和设置此次查询的一些基本属性
// SimpleExecutor的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.query(stmt, resultHandler);//查询并且映射结果集
} finally {
closeStatement(stmt);
}
}
复制代码
设置sql,设置属性
//BaseStatementHandler的prepare方法
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//实例化和初始化,这里会调用Connection的 connection.prepareStatement(sql)方法,设置原始的sql
statement = instantiateStatement(connection);
//设置statement属性
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
复制代码
调用ParameterHandler
来给设置此次查询的参数。
// DefaultParameterHandler的方法,
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//得到参数的映射 ,这里会解析参数的类型,将实际参数的类型转变为JDBC认可的类型。
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
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);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//在上面解析完参数并且确定好类型之后,这里会通过实际的typeHandler来PreparedStatement对应的位置设置参数
//注意这里的+1操作。原始的JDBC里面设置参数是从下标1开始的。之前已经将需要查询的sql已经设置进去了。
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
复制代码
执行查询并且返回结果
执行查询
//PreparedStatementHandler方法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//waring 这里会真正的执行sql,下面就开始处理结果集
ps.execute();
//处理结果集映射
return resultSetHandler.handleResultSets(ps);
}
复制代码
返回结果,处理结果集映射。
关于这里的代码,我没有详细的看过,之后看完之后会有详细分析一下。
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//这个集合就是用来存放sql查询结果之后,封装好的对象。
final List<Object> multipleResults = new ArrayList<>();
//将结果包装成ResultSetWrapper,统计了每一列的类型和名字,还有对应的jdbc的数据类型
int resultSetCount = 0;
// 将 statement的ResultSet包装为ResultSetWrapper。
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults); //将上面的改变,合并为一个单一的list。
}
复制代码
Mybatis中获取获取代理对象的之后的执行操作就分析到这里了。如有不正确的地方,欢迎指出。谢谢。