MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
在源码解析篇三中,我们已经得到了SqlSession。长征已经走了一半,前途一片光明。此篇中我们要解析下如何使用SqlSession去进行CRUD(创建(Create)、查询(Retrieve)(重新得到数据)、更新(Update)和删除(Delete))。
我们结合debug过程,逐渐深入源码解析。
一、从getMapper说起
在我们入门示例中,使用sqlSession.getMapper(MalltUserDao.class)获取到了MalltUserDao,那这中间经历了什么?下面是整个执行过程的时序图。
我们从sqlSession的getMapper方法开始深入,源码如下,源码在DefaultSqlSession类中:
public <T> T getMapper(Class<T> type) { return this.configuration.getMapper(type, this); }
这里,调用了configuration对象中的getMapper方法,传递了两个参数,一个是类类型,一个是sqlSession对象,源码如下,源码在Configuration类:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }
在这里又调用了MapperRegistry类的getMapper方法,我们看下源码:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
分析一下。
第一句:
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
其中,knownMappers是在解析核心配置文件时,使用this.mapperElement(root.evalNode("mappers"));解析的,其中最终的核心方法就是addMapper,这个我们在解析<mappers>元素时候说,这里不是我们的重点。从这里我们可以知道,knownMappers中放的就是类类型和根据类类型得到的MapperProxy工厂类MapperProxyFactory。那么MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); 得到的就是类类型对应的MapperProxy工厂类。我们这里的类类型是interface com.zhaodf.dao.MalltUserDao。
第二句: mapperProxyFactory.newInstance(sqlSession);
在此部分,使用了mapperProxy工厂类,使用我们原来得到的sqlSession,去得到我们的接口代理类。源码如下:
public T newInstance(SqlSession sqlSession) { MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); }我们分析下:
第一句:
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
使用我们传递的参数sqlSession,以及代理工厂类自身已经设置好的mapperInterface(com.zhaodf.dao.MalltUserDao),还有methodCache,这个我们没用到,它的map大小为空。
第二句:
this.newInstance(mapperProxy);
这句,使用了MapperProxyFactory类中的newInstance方法,源码如下:
protected T newInstance(MapperProxy<T> mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); }这里就是典型的代理模式,将MalltUserDao通过类加载器,转变为代理类(代理类中包含了Mapper中所有要执行的方法以及构造函数等)。过程如下:
这样,经过一整个过程的getMapper方法,得到了我们接口类的代理类,代理类的内容如下图:
下一步,我们就要根据代理类去执行我们的CRUD方法了。
二、从执行CRUD说起
得到代理类后,那我们就可以开始执行CRUD方法了。我们先把整个执行流程图画上:
当我们执行到MalltUser user = malltUserDao.findMalltUserById(9);时,这时,代理类会去调用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 var5) { throw ExceptionUtil.unwrapThrowable(var5); } } else { MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); } }其中,proxy为我们的代理类,
method为原接口中的方法对象:
args为方法调用参数:
第一句if判断,if (Object.class.equals(method.getDeclaringClass())) ,因为这里method的clazz为MalltUserDao,因此,这里肯定是不成立的。
走到else,MapperMethod mapperMethod = this.cachedMapperMethod(method);这里就需要分开详细说下。
1、this.cachedMapperMethod(method);
这里调用了cachedMapperMethod方法去得到MapperMethod,我们看下这个方法源码:
private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); this.methodCache.put(method, mapperMethod); } return mapperMethod; }因为我们的methodCache是个空的map,因此执行MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method)得到的mapperMethod为null。
继续执行if判断中内容。
在if判断体内,初始化了MapperMethod对象,我们看下它做了些什么。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new MapperMethod.MethodSignature(config, method); }这里初始化了两个MapperMethod的静态内部类,SqlCommand和MethodSignature。
我们先看下SqlCommand的构造函数:
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException { String statementName = mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null; if (configuration.hasStatement(statementName)) { ms = configuration.getMappedStatement(statementName); } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); if (configuration.hasStatement(parentStatementName)) { ms = configuration.getMappedStatement(parentStatementName); } } if (ms == null) { throw new BindingException("Invalid bound statement (not found): " + statementName); } else { this.name = ms.getId(); this.type = ms.getSqlCommandType(); if (this.type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + this.name); } } }
因为在前期获取到configuration对象中,解析mapper标签时,设置了mapperStatement,如下图:
因此,这里得到的MapperStatement就是上面图的value值。执行完SqlCommand的构造函数后,得到的各参数内容如下:
设置完的SqlCommand,name为:com.zhaodf.dao.MalltUserDao.findMalltUserById;
type为SELECT。
我们再看下另外一个静态内部类干了些啥,MethodSignature的构造函数源码如下:
public MethodSignature(Configuration configuration, Method method) throws BindingException { this.returnType = method.getReturnType(); this.returnsVoid = Void.TYPE.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.mapKey = this.getMapKey(method); this.returnsMap = this.mapKey != null; this.hasNamedParameters = this.hasNamedParams(method); this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class); this.params = Collections.unmodifiableSortedMap(this.getParams(method, this.hasNamedParameters)); }我们逐行来说明。我们先看下method对象的内容:
- this.returnType = method.getReturnType();获取方法的返回类型就是com.zhaodf.model.MalltUser。
- this.returnsVoid = Void.TYPE.equals(this.returnType);我们返回的不是void,因此returnsVoid 为false。
- this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();这里,我们的返回类型是对象,不是数组和集合,因此returnsMany为false。
- this.mapKey = this.getMapKey(method);在getMapKey的方法源码中,调用了类类型对象的isAssignableFrom方法,源码如下:
private String getMapKey(Method method) { String mapKey = null; if (Map.class.isAssignableFrom(method.getReturnType())) { MapKey mapKeyAnnotation = (MapKey)method.getAnnotation(MapKey.class); if (mapKeyAnnotation != null) { mapKey = mapKeyAnnotation.value(); } } return mapKey; }
这里的isAssignableFrom方法是一个本地方法,关于该方法的执行解释如下:有两个Class类型的类象,一个是调用isAssignableFrom方法的类对象(后称对象a),以及方法中作为参数的这个类对象(称之为对象b),这两个对象如果满足以下条件则返回true,否则返回false: a对象所对应类信息是b对象所对应的类信息的父类或者是父接口,简单理解即a是b的父类或接口 a对象所对应类信息与b对象所对应的类信息相同,简单理解即a和b为同一个类或同一个接口。因此if不成立,得到的mapKey为null。 - this.returnsMap = this.mapKey != null;因为mapKey为null,因此returnsMap 为false。
- this.hasNamedParameters = this.hasNamedParams(method);这里我们没有使用参数注解,因此hasNamedParameters为false。
- this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
得到的index为null。 - this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);与上一步一样,得到的resultHandlerIndex 也为null。
- this.params = Collections.unmodifiableSortedMap(this.getParams(method, this.hasNamedParameters));这一步得到方法传递参数的map(经过排序的)--SortedMap
这样最后设置完的MethodSignature的内容如下:
这样,我们的MapperMethod初始化完成,然后将该MapperMethod放在了methodCache中,内容如下:
2、经过上面一系列的步骤,终于到了执行的时候了。mapperMethod.execute(this.sqlSession, args);
源码如下:
public Object execute(SqlSession sqlSession, Object[] args) { Object param; Object result; if (SqlCommandType.INSERT == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); } else if (SqlCommandType.UPDATE == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); } else if (SqlCommandType.DELETE == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); } else { if (SqlCommandType.SELECT != this.command.getType()) { throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); } } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }源码内容很多,其实很简单,就是根据我们步骤1中得到的command,去执行不同的调用。我们现在得到的是SELECT,因此,直接找到最后一步的else。根据步骤1得到的MapperMethod.MethodSignature method中的各项参数,我们逐步判断,走到了最后:
param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param);
我们逐句分析下。
- 在方法this.method.convertArgsToSqlCommandParam(args)中,按照方法含义,就是将我们传递的实际参数值传递到我们的sql语句中,源码如下:
public Object convertArgsToSqlCommandParam(Object[] args) { int paramCount = this.params.size(); if (args != null && paramCount != 0) { if (!this.hasNamedParameters && paramCount == 1) { return args[(Integer)this.params.keySet().iterator().next()]; } else { Map<String, Object> param = new MapperMethod.ParamMap(); int i = 0; for(Iterator i$ = this.params.entrySet().iterator(); i$.hasNext(); ++i) { Entry<Integer, String> entry = (Entry)i$.next(); param.put(entry.getValue(), args[(Integer)entry.getKey()]); String genericParamName = "param" + String.valueOf(i + 1); if (!param.containsKey(genericParamName)) { param.put(genericParamName, args[(Integer)entry.getKey()]); } } return param; } } else { return null; } }因为我们的args参数数组的大小为1,且根据我们构造MapperMethod.MethodSignature method得到的hasNamedParameters为false,因此直接走了 return args[(Integer)this.params.keySet().iterator().next()];
得到的param=9。
- 在方法result = sqlSession.selectOne(this.command.getName(), param);中,我们调用了sqlSession的selectOne方法。这里的this.command.getName()为com.zhaodf.dao.MalltUserDao.findMalltUserById,param就是第一步得到的param--Object对象,内容为9。DefaultSqlSession的selectOne源码如下:
public <T> T selectOne(String statement, Object parameter) { List<T> 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; } }
方法里调用了this.selectList(statement, parameter);源码如下:public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }
继续往下找重载的selectList方法,源码如下:public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List var6; try { MappedStatement ms = this.configuration.getMappedStatement(statement); List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); var6 = result; } catch (Exception var10) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10); } finally { ErrorContext.instance().reset(); } return var6; }
我们逐句分析,MappedStatement ms = this.configuration.getMappedStatement(statement);这里就是根据我们在mapper配置文件中的唯一性id去得到对应的MappedStatement。内容如下:public MappedStatement getMappedStatement(String id) { return this.getMappedStatement(id, true); } public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { this.buildAllStatements(); } return (MappedStatement)this.mappedStatements.get(id); }
得到的MappedStatement如下图:
接着,执行List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);这里面包含了4个参数,第一个就是我们上一步得到的ms,第二个参数,就是将参数对象根据所属的不同实例类型进行包装,源码如下:private Object wrapCollection(Object object) { DefaultSqlSession.StrictMap map; if (object instanceof List) { map = new DefaultSqlSession.StrictMap(); map.put("list", object); return map; } else if (object != null && object.getClass().isArray()) { map = new DefaultSqlSession.StrictMap(); map.put("array", object); return map; } else { return object; } }
因为我们这里的Object实际内容是Integer的9,因此,返回的object还是Integer 9。所有参数确定后,我们看下CachingExecutor中query方法的源码:public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
在源码中,第一句BoundSql boundSql = ms.getBoundSql(parameterObject);根据传递的参数对象,获取到BoundSql。BoundSql 包含了sql语句、参数等信息:
第二句中,CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);得到该语句的缓存key值,见下图:
第三句中,调用了CachingExecutor中重载的方法,this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);并将结果返回。我们看下源码: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) { this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, parameterObject, boundSql); List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
这里,我们的cache为null,因此直接调用了BaseExecutor类中的query方法,看下源码: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 (this.closed) { throw new ExecutorException("Executor was closed."); } else { if (this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; list = resultHandler == null ? (List)this.localCache.getObject(key) : null; if (list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { Iterator i$ = this.deferredLoads.iterator(); while(i$.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next(); deferredLoad.load(); } this.deferredLoads.clear(); if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }
我们直接看关键一句:list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER); List list; try { list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { this.localCache.removeObject(key); } this.localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { this.localOutputParameterCache.putObject(key, parameter); } return list; }
这里关键代码也只有一句,list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);是用来做查询的,其他操作只是将我们查询出来的结果缓存,放在localCache中。我们分析下doQuery方法,这个方法在SimpleExecutor类中,我们去看看:public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; List var9; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = this.prepareStatement(handler, ms.getStatementLog()); var9 = handler.query(stmt, resultHandler); } finally { this.closeStatement(stmt); } return var9; }
这里使用ms获取到我们configuration,然后使用configuration去得到对应的Statement处理器,关键的代码如下: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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler); return statementHandler; }
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch(ms.getStatementType()) { case STATEMENT: this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }
代码看似很多,实际上就做了一个动作,根据我们statementType(有三种:STATEMENT,PREPARED 或 CALLABLE ,默认情况下是PREPARED )去得到不同的statement处理器,因此我们这里得到的是PreparedStatementHandler。在PreparedStatementHandler中,又初始化了参数处理器和结果集处理器:
我们看下最关键的一行代码,var9 = handler.query(stmt, resultHandler);源码追踪,最后跟踪到PreparedStatementHandler类的query方法,这里跟jdbc处理的方法一样:public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement)statement; ps.execute(); return this.resultSetHandler.handleResultSets(ps); }
this.resultSetHandler.handleResultSets(ps)结果集处理的源码如下:public List<Object> handleResultSets(Statement stmt) throws SQLException { List<Object> multipleResults = new ArrayList(); int resultSetCount = 0; ResultSetWrapper rsw = this.getFirstResultSet(stmt); List<ResultMap> resultMaps = this.mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); this.validateResultMapsCount(rsw, resultMapCount); while(rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount); this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null); rsw = this.getNextResultSet(stmt); this.cleanUpAfterHandlingResultSet(); ++resultSetCount; } String[] resultSets = this.mappedStatement.getResulSets(); if (resultSets != null) { while(rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId); this.handleResultSet(rsw, resultMap, (List)null, parentMapping); } rsw = this.getNextResultSet(stmt); this.cleanUpAfterHandlingResultSet(); ++resultSetCount; } } return this.collapseSingleResultList(multipleResults); }
三、通过以上分析,我们对Mybatis中执行CRUD的过程从源码层面上有了了解。内容比较多,能坚持下来也是种进步。
这里涉及到代理模式,后续我们会分享一篇代理模式的帖子。