mybatis原理解析---SqlSession运行过程(上)

sqlSession代表与数据库的一次会话,在这次会话中可以多次执行查询等sql操作。从前面可以看到SqlSession对象是从SqlSessionFactory对象中获得的。sqlSession本身就定义了一系列的update select delete insert等方法,在旧版本的mybatis中是直接调用这些方法的,但是在mybatis3中先通过getMapper()获取到mapper对象(一个代理对象),然后通过mapper来调用相应的sql。如下面所示:

//通过SqlSession对象获取BranchMapper接口的代理对象,并通过代理对象执行sql获取结果
BranchMapper branchMapper=sqlSession.getMapper(BranchMapper.class);
branchMapper.getBranchByNameAndCity("test","bj");

正如上面注释所示,本质上是通过sqlSession的getMapper()方法拿到了接口的代理对象,然后通过这个代理对象去执行的相应的sql。下面从源码来看下代理对象的构建过程。

 @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

可以看到生成代理对象调用的是Proxy.newProxyInstance()方法,而真正的代理对象是MapperProxy类。下面来看下MapperProxy类的源码,下面是部分源码:

public class MapperProxy<T> implements InvocationHandler, Serializable {

   private static final long serialVersionUID = -6424540398559729838L;
   private final SqlSession sqlSession;
   private final Class mapperInterface;
   private final Map methodCache;

   public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
     this.sqlSession = sqlSession;
     this.mapperInterface = mapperInterface;
     this.methodCache = methodCache;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     try {
       if (Object.class.equals(method.getDeclaringClass())) {
         return method.invoke(this, args);
       } else if (isDefaultMethod(method)) {
         return invokeDefaultMethod(proxy, method, args);
       }
     } catch (Throwable t) {
       throw ExceptionUtil.unwrapThrowable(t);
     }
     //cachedMapperMethod为method生成MapperMethod对象,并且还会将生成的对象缓存到methodCache中,这样下次再调用同样的
     //方法就不需要重新生成了
     final MapperMethod mapperMethod = cachedMapperMethod(method);
     return mapperMethod.execute(sqlSession, args);
   }
}

首先来看下里面的几个属性:
sqlSession: 本次执行的数据库会话,即sqlSession.getMapper(BranchMapper.class)中的sqlSession。
mapperInterface: sqlSession.getMapper(BranchMapper.class)传入的BranchMapper.class,即需要被代理的Class对象。
methodCache:是一个Map对象:Map,key是BranchMapper接口中的method,value是一个代理方法MapperMethod对象,为接口中的每个方法都缓存了一个代理方法对象。
通过MapperMethod类的源码可以看到其有两个属性:
private final SqlCommand command;
private final MethodSignature method;
MethodSignature里面存储的是传入方法的一些信息像返回值 传入参数等信息。SqlCommand的源码如下:

public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class declaringClass = method.getDeclaringClass();
      //通过mapperInterface的全限定类名和传入的方法名组合成的key,可以从configuration中获取到对应的MappedStatement
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

可以看到里面最重要的逻辑就是获取到当前接口方法对应的MappedStatement对象,然后将这个对象的id作为SqlCommand的名称;这样设置之后,就可以通过Method对应MappedMethod中的SqlCommand对象的name属性获取到关联的MappedStatement对象。methodCache不是在初始化的时候进行填充的,而是在真正调用接口的方法时在invoke()方法里进行初始化的,MappedMethod中的MethodSignature对象(描述方法的入参 返回值 解析方法参数名(包括@Param注解的参数名都是这个对象实现的))也是在invoke()方法里实现的。
通过sqlSession.getMapper()方法拿到的是mapper是个代理对象,执行代理对象的相关方法的时候,比如执行branchMapper.getBranchByNameAndCity(“test”,”bj”);会先调用代理类的invoke()方法。在上面的MapperProxy的invoke()方法中,先判断传入的method是不是属于一个类或者是不是默认的方法,如果不是,那么就为当前的method生成一个MappedMethod对象,然后执行其execute()方法,将当前的sqlSession和方法参数传入。下面来看下execute()内部的逻辑:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {//返回多个结果的查询
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

可以看到在这个方法里面会根据传入的查询类型和返回结果的不同,分别调用不同的方法来执行sql(使用了命令模式)。但这些方法里都是调用了sqlSession相应的方法进行的查询,比如说最常用的返回多个值的查询:

//返回多个结果的查询
else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);

下面是executeForMany()方法的源码,可以看到里面其实就是调用了sqlSession的selectList()方法完成的查询:

private  Object executeForMany(SqlSession sqlSession, Object[] args) {
    List result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

然后再看下sqlSession.selectList(command.getName(), param)的内部实现,它里面调用的是下面这个方法:

@Override
  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

传入的参数statement是MapperMethod中SqlCommand对象的name即Mapper接口方法对应的MappedStatement的id,parameter是MappedMethod中通过组合传入的Method中参数名称(@Param注解的参数名等)信息 参数值信息等得到的参数对象。这个方法里会通过Configuration通过id拿到MappedStatement对象,然后通过与sqlSession关联的executor.query()方法来执行查询。
这里有通过wrapCollection对传入的parameter对象再进行一次处理,下面是具体的处理过程:

private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap map = new StrictMap();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap map = new StrictMap();
      map.put("array", object);
      return map;
    }
    return object;
  } 
  

主要针对方法的参数类型是Collection List Array这三种情况并且没有使用@Param注解的时候,提供默认的名称值;这样设置之后,可以在mapper.xml中直接通过#{list} #{collection} #{array}等方式进行直接引用。

现在来简单总结下,前面的一系列过程完成了传入参数的转换:将入参数组args[]结合method对象中关于参数的信息得到了parameter对象,通过传入method对象的全限定类名加方法名取得了关联的MappedStatement对象(完成了接口方法和mapper.xml配置sql的管理)。完成了以上两步之后就完成了sql运行前的准备工作,接下来就是通过SqlSession中executor来执行sql获取并转化查询结果了。

你可能感兴趣的:(数据库,mybatis,sql,深入学习mybatis)