欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析

程序员文章站 2022-07-12 20:36:46
...

在源码解析篇三中,我们已经得到了SqlSession。长征已经走了一半,前途一片光明。此篇中我们要解析下如何使用SqlSession去进行CRUD(创建(Create)、查询(Retrieve)(重新得到数据)、更新(Update)和删除(Delete))。

我们结合debug过程,逐渐深入源码解析。

一、从getMapper说起

在我们入门示例中,使用sqlSession.getMapper(MalltUserDao.class)获取到了MalltUserDao,那这中间经历了什么?下面是整个执行过程的时序图。

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

我们从sqlSession的getMapper方法开始深入,源码如下,源码在DefaultSqlSession类中:

 

public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

 

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
这里,调用了configuration对象中的getMapper方法,传递了两个参数,一个是类类型,一个是sqlSession对象,源码如下,源码在Configuration类:

 

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

 

在这里又调用了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);
            }
        }
    }

分析一下。

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

第一句: 

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。

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

第二句: mapperProxyFactory.newInstance(sqlSession);

在此部分,使用了mapperProxy工厂类,使用我们原来得到的sqlSession,去得到我们的接口代理类。源码如下:

 

public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
 我们分析下:

 

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

第一句:

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中所有要执行的方法以及构造函数等)。过程如下:

 

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

这样,经过一整个过程的getMapper方法,得到了我们接口类的代理类,代理类的内容如下图:

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

下一步,我们就要根据代理类去执行我们的CRUD方法了。

 

 

二、从执行CRUD说起

得到代理类后,那我们就可以开始执行CRUD方法了。我们先把整个执行流程图画上:


MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码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为我们的代理类,

 

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

method为原接口中的方法对象:

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 

args为方法调用参数:

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 


 第一句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判断中内容。

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 在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,如下图:

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 因此,这里得到的MapperStatement就是上面图的value值。执行完SqlCommand的构造函数后,得到的各参数内容如下:

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

设置完的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对象的内容:

 

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 

  • 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);MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
     得到的index为null。
  • this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);与上一步一样,得到的resultHandlerIndex 也为null。
  • this.params = Collections.unmodifiableSortedMap(this.getParams(method, this.hasNamedParameters));这一步得到方法传递参数的map(经过排序的)--SortedMap

这样最后设置完的MethodSignature的内容如下:

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

这样,我们的MapperMethod初始化完成,然后将该MapperMethod放在了methodCache中,内容如下:

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

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。

MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
 

 

  • 在方法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;
            }
        }
    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
      方法里调用了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如下图:
    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
     接着,执行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语句、参数等信息:
    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
      第二句中,CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);得到该语句的缓存key值,见下图:
    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
     第三句中,调用了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);
        }
     
    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
    这里,我们的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中,又初始化了参数处理器和结果集处理器:
    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 

    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
     我们看下最关键的一行代码,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);
        }
     
    MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
     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源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 

三、通过以上分析,我们对Mybatis中执行CRUD的过程从源码层面上有了了解。内容比较多,能坚持下来也是种进步。

这里涉及到代理模式,后续我们会分享一篇代理模式的帖子。

 

  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 8.7 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 9.5 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 27.5 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 25.8 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 7.7 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 10.1 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 37.9 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 39 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 45 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 6.4 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 6.1 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 10.2 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 15.6 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 35.8 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 41.9 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 26.1 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 21.2 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 30.6 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 25.7 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 8 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 17.9 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 29.1 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 31.4 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 4 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 27.5 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 33.7 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 43.5 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 19.5 KB
  • MyBatis源码解析系列(四)--使用SqlSession去进行CRUD解析
            
    
    博客分类: Mybatis Mybatis源码CRUD 
  • 大小: 53.4 KB