Mybatis操作数据库主要步骤
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(2L);
1.通过动态代理获取Mapper对象
从debug可以看出,knownMapper已经初始化代码中所有的mapper对象
具体如何初始化数据后续作答
从MapperProxyFactory的实例化方法中可以看出,所有的Mapper对象都是通过MapperProxy<T>代理对象中获取的mapper对象
说明MapperProxy是通过jdk动态代理实现,既然说MapperProxy是个代理对象,从以上可以看出我们已经获取了Mapper的代理对象
2.获取mapperMethod对象(SqlCommand,Signature)
上文中说到MapperProxy是个代理类,那么代理类所有的方法执行都会经过invoke方法,现在让我们来看看它的invoke方法
MapperMethod mapperMethod = this.cachedMapperMethod(method);
由源码可以看出,这步已经获取了MapperMethod对象,那我们看看源码是怎么获取的
在缓存methodCache获取,若没有,则创建MapperMethod对象,并且添加到缓存中,接下来看看MapperMethod构造有些操作
我们先来看看sql指令
debug可以看出,调用代理类的方法时,所有经过invoke的方法信息已经携带进来了
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
可以看出sqlCommand的构造主要操作都是获取MapperStatement这个对象
拼接statementId = "mapper.UserMapper.getUserById",接下来看看具体用这个操作了啥
我们先不管这个Maps定义是啥,debug来看这个目前都是空
看了这么多,实际上就是判断之前Map<String,MapperStatement>之前是否存在getUserById这个mapper和方法,存在的话之前将Map中的MapperStatement返回
那么问题来了,我们之前也没有往里面put元素,看来mybatis在加载的时候已经做了初始化操作,具体初始化后续再说
当然如果MapperStatement不存在的话,获取该类UserMapper.Class所有的接口中是否存在对应的MapperStatement,不存在直接返回null
当然如果mapperStateMent不存在的话,若该方法上存在@Flush注解,说明该次执行flush操作,
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
从以前我们可以看出sqlCommand主要包含了方法名称和操作数据库方式(select,update,delete,insert,flush)
那么我们再来看看方法签名MethodSignature构造具体有哪些操作
可以看出方法签名的构造主要是为了一堆属性赋值操作,那我们来看看这些属性代表啥
returnType:方法返回值类型
returnsVoid:返回值是否为void类型(方法是否有返回值)
returnsMany:返回值是否是数组或集合
returnsCursor:返回值是否cursor类型
returnsMap:返回值是否是Map类型
rowBoundsIndex:入参为RowBounds的下标号
resultHandleIndex:入参为ResultHandle的下标号
最后生成paramNameResolve实例对象
/**
* 解析方法入参,维护到names中。
*/
public ParamNameResolver(Configuration config, Method method) {
// eg1: paramTypes[0] = Long.class
final Class<?>[] paramTypes = method.getParameterTypes();
// eg1: paramAnnotations[0][0] = @org.apache.ibatis.annotations.Param(value=id)
/**
* 首先举个例子:
* @RedisScan
* public void save(@RedisSave() int id, @RedisSave() String name){
* ... ...
* }
*
* Annotation[][] annos = method.getParameterAnnotations();
* 二维数组中:第一个参数下标为0,第二参数下标为1
* 即:annos[0][0]=RedisSave 和 annos[1][0]=RedisSave,也就是说,二维数组是包含多个仅有一个值的数组。
* 因为参数前可以添加多个注解,所以是二维数组;一个参数上不可以添加相同的注解,同一个注解可以加在不同的参数上。
*/
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
// eg1: paramCount = 1
int paramCount = paramAnnotations.length;
/**
* get names from @Param annotations
*/
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 判断是否是特殊的参数——即:RowBounds.class或ResultHandler.class
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
// eg1: paramAnnotations[0] = @org.apache.ibatis.annotations.Param(value=id)
/** 使用@Param指定的入参名称 */
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
// eg1: name = "id"
name = ((Param) annotation).value();
break;
}
}
/** 没有使用@Param指定的入参名称 */
if (name == null) {
/** @Param was not specified;useActualParamName默认值为true*/
if (config.isUseActualParamName()) {
/** use the parameter index as the name ("arg0", "arg1", ...) */
name = getActualParamName(method, paramIndex);
}
if (name == null) {
/** use the parameter index as the name ("0", "1", ...) */
name = String.valueOf(map.size());
}
}
// eg1: paramIndex=0 name="id"
map.put(paramIndex, name);
}
// eg1: names={0:"id"}
names = Collections.unmodifiableSortedMap(map);
}
让我们看看代码具体是怎么生成paramNamesResolve
主要是判断循环判断入参是否存在@param注解,若存在 map.put("参数下标",@Param注解自定义的value),如不存在@Param注解 map.put("参数下标","参数名称")
现在来看我我们已经获取mapperMethod对象已经构造完毕了,再看mapperMethod执行的时候具体有哪些操作
3.根据MapperMethod构造跳转执行语句
/**
* MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中。实际上它最后就是通过SqlSession对象去运行对象的SQL。
*/
// eg1: sqlSession = DefaultSqlSession@1953 args = {2L}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// eg1: command.getType() = SELECT
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:
// eg1: method.returnsVoid() = false method.hasResultHandler() = false
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) { // eg1: method.returnsMany() = false
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) { // eg1: method.returnsMap() = false
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) { // eg1: method.returnsCursor() = false
result = executeForCursor(sqlSession, args);
} else {
// eg1: args = {2L}
/** 将参数转换为sql语句需要的入参 */
Object param = method.convertArgsToSqlCommandParam(args);
// eg1: sqlSession=DefaultSqlSession command.getName()="mapper.UserMapper.getUserById" param={"id":2L, "param1":2L}
/** 执行sql查询操作 */
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;
}
由以上可以看出,根据我们之前构造的sqlCommand(sql指令)判断操作数据库的类型,我们现在以getUserById(SELECT)为例
param = this.method.convertArgsToSqlCommandParam(args);
然后再根据args参数转为sql需要的参数
param={"id":2L, "param1":2L}
最后再根据返回值类型判断,sqlSession需要执行的方法
以上步骤主要是为了解析自定义的Mapper对象和方法,最终查询数据库操作是由sqlSession来操作
4.sqlSession查询前的缓存操作(selectOne为例)
可以看出selectOne也是通过selectList获取第一个元素,然而不是一个元素的话,直接抛出异常
可以看出这个MappedStatement又出现了,之前说过
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
这个字段在加载的时候mybatis已经初始化了,那么接下来就是调用Executor执行器来执行查询操作
可以看出查询之前会先执行CachingExecutor(缓存),从缓存中查询数据,那么我们来看看这个缓存又做了那些操作
Cache cache = ms.getCache();
如果在UserMapper.xml配置了<cache/>开启了二级缓存,则cache不为null
判断flushCacheRequired是否刷新二级缓存
判断UserCache是否将本条sql结果进行二级缓存
SELECT:flushCacheRequired默认false,UserCache默认true
UPDATE.Delete,insert:flushCacheRequired默认为true
由以上代码可以看出,二级缓存的数据是从Cache对象中获取的
但是源码中看,从数据库中查询的数据只会维护到TransactionalCache的本地Map中,根本没有维护Cache缓存,那么这个时候就需要commit作用就来了
只有在commit之后,二级缓存才会真正写入
接下来如果二级缓存没有开启/没有数据,我们来看看查询数据库有哪些操作呢
1.根据入参和ResultMap构建BoundSql,所以我们之前在log看到的sql语句都是通过getBoundSql方法构造的
// eg1: parameterObject = {"id":2L, "param1":2L}
public BoundSql getBoundSql(Object parameterObject) {
// eg1: sqlSource = RawSqlSource parameterObject = {"id":2L, "param1":2L}
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// eg1: parameterMappings[0] = {property='id', mode=IN, javaType=class java.lang.Long, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// eg1: parameterMappings不为空
if (parameterMappings == null || parameterMappings.isEmpty()) {
/** 如果boundSql里的parameterMappings为空,那么用parameterMap的parameterMappings再次构建权限的BoundSql对象 */
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
/**
* 如果配置了resultMap,则判断赋值hasNestedResultMaps,判断是否有聚合的结果集
*/
for (ParameterMapping pm : boundSql.getParameterMappings()) {
// eg1: rmId = resultMapId = null 由于UserMapper.xml中配置的是resultType="vo.User"而不是resultMap,所以rmId=null
String rmId = pm.getResultMapId();
// eg1: rmId = null
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
那么现在就有人问问啥我写的#{},为啥变成了?方式拼接
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
char[] src = text.toCharArray();
int offset = 0;
// search open token
int start = text.indexOf(openToken, offset);
if (start == -1) {
return text;
}
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
具体是怎么拼接的你们可以自己研究研究,这里暂不做说明
// eg1: parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
@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());
// eg1: closed = false
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// eg1: queryStack = 0 ms.isFlushCacheRequired() = false
/** 如果配置了flushCacheRequired=true并且queryStack=0(没有正在执行的查询操作),则会执行清空缓存操作*/
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
/** 记录正在执行查询操作的任务数*/
queryStack++; // eg1: queryStack=1
// eg1: resultHandler=null localCache.getObject(key)=null
/** localCache维护一级缓存,试图从一级缓存中获取结果数据,如果有数据,则返回结果;如果没有数据,再执行queryFromDatabase */
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// eg1: list = null
if (list != null) {
/** 如果是执行存储过程 */
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// eg1: parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
/** 延迟加载处理 */
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// eg1: configuration.getLocalCacheScope()=SESSION
/** 如果设置了<setting name="localCacheScope" value="STATEMENT"/>,则会每次执行完清空缓存。即:使得一级缓存失效 */
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
1.如果配置了FlushCacheRequired且没有正在执行的sql语句时,先清空缓存
2.记录正在执行sql标记
3.localCache维护一级缓存,试图从一级缓存中获取结果数据,如果有数据,则返回结果;如果没有数据,再执行queryFromDatabase,将查询的数据再缓存到一级缓存localCache
4.判断该次statementType是否为存储过程 ==> 该章不做讲解
5.延迟加载处理
6.如果设置了<setting name="localCacheScope" value="STATEMENT"/>,则会每次执行完清空缓存。即:使得一级缓存失效
步骤总结:该步骤主要是为了做查询前的缓存操作 CacheExecutor维护二级缓存 localCache维护一级缓存
5.DB操作
// eg1: parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
@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();
/** 根据Configuration来构建StatementHandler */
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
resultHandler, boundSql);
// eg1: handler=RoutingStatementHandler
/** 然后使用prepareStatement方法,对SQL进行预编译并设置入参 */
stmt = prepareStatement(handler, ms.getStatementLog());
// eg1: handler=RoutingStatementHandler parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
/** 开始执行真正的查询操作。将包装好的Statement通过StatementHandler来执行,并把结果传递给resultHandler */
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
1.根据configuration构建PrepareStatementHandler
// eg1: parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
/**
* StatementType: STATEMENT, PREPARED, CALLABLE
*/
// eg1: ms.getStatementType() = PREPARED
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// eg1: parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
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());
}
}
从这行代码可以看出我们现在获取的StatementHandler为预编译处理器,那么预编译处理器又是怎么构造的呢
// eg1: parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
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();
// eg1: boundSql不为空,不执行该段逻辑
if (boundSql == null) {
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);
}
最终只是为从BaseStatementHandler处理器中获取预编译处理器PrepareStatementHandler,说实话这块我看着我觉得没啥用
2.对获取的预编译处理器PrepareStatementHandler进行拦截处理
/**
* 构建RoutingStatementHandler,并添加到拦截器链interceptorChain中
*/
// eg1: parameter = {"id": 2L, "param1", 2L} rowBounds = new RowBounds() resultHandler = null
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);
// eg1: interceptorChain中是空Chain,由于interceptorChain中没有执行addInterceptor()来添加Interceptor,所以执行pluginAll这个也是徒劳的。
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
// eg1: statementHandler=PreparedStatementHandler
return statementHandler;
}
这段代码主要是为了加载配置文件中自定义的拦截器,目前我们先不考虑拦截器情况,所以这行代码对我们没啥影响
以上代码主要是addInterceptor的方式
3.使用PrepareStatementHandler设置入参
/**
* 使用prepareStatement方法,对SQL编译并设置入参
*
* @param handler
* @param statementLog
* @return
* @throws SQLException
*/
// eg1: handler=RoutingStatementHandler
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
/** 获得Connection实例 */
Connection connection = getConnection(statementLog);
// eg1: handler=RoutingStatementHandler
/** 第一步:调用了StatementHandler的prepared进行了【sql的预编译】 */
stmt = handler.prepare(connection, transaction.getTimeout());
/** 第二步:通过PreparedStatementHandler的parameterize来给【sql设置入参】 */
handler.parameterize(stmt);
// eg1: 返回org.apache.ibatis.logging.jdbc.PreparedStatementLogger@2e570ded
return stmt;
}
1.先根据connection,准备预编译语句
// eg1: connection
/**
* 准备预编译语句
*/
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// eg1: sql="select id, name, age from tb_user where id = ?"
String sql = boundSql.getSql();
// eg1: mappedStatement.getKeyGenerator=NoKeyGenerator
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
}
// eg1: mappedStatement.getResultSetType() = null
else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(),
ResultSet.CONCUR_READ_ONLY);
} else {
// eg1: sql="select id, name, age from tb_user where id = ?"
/** 准备预编译语句 */
return connection.prepareStatement(sql);
}
}
1.主键生成策略我们目前没有,暂时无需考虑
2.我们设置的select语句返回类型为ResultType,所以也不用考虑
3.调用JDBCconnection准备预编译语句
/** 准备预编译语句 */ return connection.prepareStatement(sql);
从以上来看,我们已经对PrepareStatementHandler设置的入参以及sql语句,接下来就可以用PrepareStatementHandler进行数据操作了
// eg1: delegate = PreparedStatementHandler resultHandler = null
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
/** 最终还是使用JDBC去进行数据操作 */
PreparedStatement ps = (PreparedStatement) statement;
/** 执行查询操作 */
ps.execute();
// eg1: 封装结果集 resultSetHandler=DefaultResultSetHandler
/** 将结果集进行封装 */
return resultSetHandler.handleResultSets(ps);
}
以上代码才是PrepareStatementHandler使用JDBC进行数据操作
6.结果集封装
/**
* 处理数据库操作的结果集
*/
// eg1: 执行到这里
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
/** 首先:获得执行后的结果集,并封装到ResultSetWrapper */
ResultSetWrapper rsw = getFirstResultSet(stmt);
/** 其次:如果rsw != null && resultMapCount < 1,则抛异常ExecutorException */
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size(); // eg1: resultMapCount = 1
validateResultMapsCount(rsw, resultMapCount);
// eg1: rsw不为空 resultMapCount=1 resultSetCount=0
/** 第三步:处理结果集 */
while (rsw != null && resultMapCount > resultSetCount) {
// eg1: ResultMap resultMap=resultMaps.get(0);
ResultMap resultMap = resultMaps.get(resultSetCount);
/** 处理结果集, 存储在multipleResults中 */
handleResultSet(rsw, resultMap, multipleResults, null);
// eg1: rsw=null
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++; // eg1: 自增后resultSetCount=1
}
String[] resultSets = mappedStatement.getResultSets();
// eg1: resultSets = null
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++;
}
}
// eg1: multipleResults.get(0).get(0) = User{id=2, name='muse2', age=24, userContacts=null}
/** 返回结果 */
return collapseSingleResultList(multipleResults);
}
1.通过JDBC通过statement获取resultSet,并将resultSet封装到ResultSetWrapper
2.校验ResultSetWrapper中是否有数据且ResultMap不能小于1
3.处理结果集ResultSetWrapper存储到multipleResults,然后在获取下一个结果集再次循环存储
4.由于我们xml中使用的ResultType返回类型,所以ResultSets情况不需要考虑 ==> ResultMap和ResultType在statement中都是ResultMaps属性
5.最后将结果集返回,至于如果想细致的查看ResultMap和ResultSetWrapper数据怎么转换可以自己跟源码进去看看
总结:
mybatis操作数据除了以上主要六大步骤之外,主要有很多数据都是在mybatis加载的时候从mybatis-config.xml配置文件以及对应的Mapper.xml文件中已经初始化好了
接下来再来看看基本的一些初始化数据
这是我们一般使用mybatis的时候都需要通过SqlSessionFactory获取Sqlsession代理对象
那么我们在构建SqlSessionFactory的时候,需要将xml文件/property文件/环境变量解析
/**
* 解析配置文件
*
* @return
*/
public Configuration parse() {
// 每个XML配置构造器实例只允许解析配置文件一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析config.xml配置文件最重要的方法。选取根节点<configuration>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 对xml的配置文件解析到Configuration对象中。
*
* @param root
*/
private void parseConfiguration(XNode root) {
try {
// 将<properties>的配置转化赋值Configuration的variables属性
propertiesElement(root.evalNode("properties"));
// 将<settings>的配置转化赋值Properties,并检查Configuration中是否有对应的属性,如果没有,抛异常,终止整个解析流程。
Properties settings = settingsAsProperties(root.evalNode("settings"));
/**
* 对settings中的vfsImp属性进行二次处理,生成实例对象,并赋值Configuration的vfsImpl属性
*
* 为什么解析<settings>中的值为Properties不能都在settingsAsProperties方法中完成,而要单独在loadCustomVfs中完成?
* 回答;由于vfsImpl配置的是Class,需要通过ClassLoader加载为对象,赋值到Configuration里,而不像其他String类型的value
* ,可以直接赋值进去并使用。所以单独在loadCustomVfs方法中进行了Configuration中的vfsImpl赋值操作。
*/
loadCustomVfs(settings);
// 将<typeAliases>的配置转化赋值Configuration的typeAliasRegistry属性
typeAliasesElement(root.evalNode("typeAliases"));
// 解析<plugins>标签,初始化Configuration的interceptorChain
pluginElement(root.evalNode("plugins"));
// 解析<objectFactory>标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析<objectWrapperFactory>标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析<reflectorFactory>标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 将配置信息中settings赋值给Configuration
settingsElement(settings);
// 解析<environments>标签
environmentsElement(root.evalNode("environments"));
// 解析<databaseIdProvider>标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析<databaseIdProvider>标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析<mappers>标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
以上代码就是解析各个mybatis-config根目录<configuration/>下的各个节点,并且最终赋值到 ==> 具体自己可以查看BaseBuilder以及Configuration的属性,以上就是mybatis初始化数据的基本流程,至于初始化操作的细节步骤,自己可以跟源码看看