[Spring] Mybatis Mapper如何关联XML

1,821 阅读7分钟

Mybatis作为一个半自动的ORM框架, 可以通过xml或者注解的方式完成SQL语句的编写, 消除了大量JDBC冗余代码,不需要手动开关连接. 让我们可以直接基于SQL编程, 不必关系数据库相关操作, 极大方便了我们的开发过程. 

在使用过程中, 我们只需要定义Mapper接口和对应的Mapper.xml文件, 通过namespace和SQL-id就可以将Mapper中的接口方法和对应的SQL语句关联起来.  

那么有没有想过, 是如何实现这个巧妙的关联过程的. 针对Spring/Mybatis的整合, 提出了以下几个问题, 让我们带着问题去探究这个巧妙的实现.

  1. Mapper接口如何被BeanFactory实例化成Bean实例的
  2. MapperBean如何关联到Mapper文件中的SQL语句的
  3. MapperBean中的数据库链接Connection如何获取的, 和事务切面中获取管理事务的代码是否一致
  4.  Mapper是否能够被再次代理, 如何对Mapper进行切面处理?
  5.  #和$的区别
  6.  Spring整合mybatis的配置,如何将Mybatis整合进入Spring的

MapperFactoryBean

(提前剧透)在Spring-Mybatis的整合中, Spring是通过FactoryBean机制:MapperFactoryBean来将Mapper_(接口)_注入到Spring容器中.  

FactoryBean#getObject()可以让我们在Spring容器getBean的时候改变对象的实例化(同样也就能改变方法的实现).  MapperFactoryBean就是利用了这点, 将Mapper注入到Spring容器, 让我们可以在Service里面注入Mapper. 

接下来就翻阅MapperFacotory的源码,看Spring是如何实例化Mapper实例的

MapperFactoryBean#getObject

 public T getObject() throws Exception {
     return this.getSqlSession().getMapper(this.mapperInterface);
 }

可以看到MapperFactoryBean#getObject将实例化的操作委托给SqlSession#geteMapper.

那么这个SqlSession是从哪里来的, 它的getMapper方法是怎么实现的?

SqlSession我们可以发现有3个实现类:

  • DefaultSqlSession
  • SqlSessionManager
  • SqlSessionTemplate

可以看到DefaultSqlSession和SqlSessionTemplate中的getMapper都是委托给Configuration#getMapper(class, SqlSession) . 

DefaultSqlSession#getMapper

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

SqlSessionTemplate#getMapper

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

我们就直接跳入Configuration#getMapper(class,sqlSession)查看是如何构建Mapper实例的. 关于如何获取SqlSession的在 SqlSession&SqlSessionFactory章节中解释. 

Configuration

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);  

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

可以看到, Configuration#getMapper依然是将委托给了mapperRegistry.

不过可以看到getMapper对应的操作有个addMapper操作,它的操作也是委托给MapperRegistry.

  public <T> void addMapper(Class<T> type) {
   mapperRegistry.addMapper(type);
  }

不过查看Configuration#getMapper的调用链,可以看到它在MapperFactoryBean#checkDaoConfig中被调用. 

  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

这段代码大致内容就是: 1. 检查SqlSession是否为空,如果为空,抛出异常  2. 判断configuration中是否存在需要获取的Mapper, 如果没有则添加(addMapper). 

checkDaoConfig继承自父类, 在InitializingBean#afterProperties实现中被调用. 这样实现就保证了MapperFactoryBean#getObject的时候不会出现异常. 

MapperRegistry

既然Configuration#getMapper和addMapper的实现都委托给了MapperRegistry,继续看MapperRegistry是怎么处理getMapper和addMapper的. 

MapperRegistry#getMapper

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

  // ....
}

可以看到getMapper(type,sqlSession)从knownMappers中获取了一个MapperProxyFactory实例, 然后通过MapperProxyFactory.newInstance(sqlSession)方法完成了Mapper的实例化. 

这里的的knownMappers的put操作在下面的MapperRegistry#addMapper中被调用.

MapperRegistry#addMapper

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
// ....
}

前面的MapperRegistry#getMapper中使用的MapperProxyFactory就是在这里被实例化的.

MapperAnnotationBuilder见名知意, 应该是处理Mapper上面的@Select @Update @Delete @Insert注解的. 

MapperProxyFactory

前面已经了解到Mapper的实例是通过MapperProxyFactory#newInstance(class)创建的. 通过命名,可以大致得到MapperProxyFactory是一个工厂,用来生产Mapper实例. 我们想要知道的Mapper实例是如何创建的,大致应该就在这里了, 再也不是委托操作了. 

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

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

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

}

MapperProxyFactory#newInstance(sqlsession)是使用的Jdk动态代理创建的Mapper实例, 对应的InvocationHandler实现对应的就是MapperProxy.  

到这里, Mapper实例如何被创建出来的就明确了, 下面需要继续了解Mapper中方法的实现是怎样的, 也就是InvocationHandler#invoke是如何在MapperProxy中实现的. 

在这里我们了解了Mapper(无实现类)对应的实例是如何被创建的, 你也可以实现一个InvokcationHadler来为一个接口创建代理实现类实例. 

public interface User {

    String getName();
}


public static void main(String[] args){

    User user =(User) Proxy.newProxyInstance(User.class.getClassLoader(), new Class[]{User.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return "Baby" ;
        }
    });

    System.out.println(user.getName());

}

MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  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 t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}

可以看到, Mapper接口Method的行为转换为MapperMethod.execute的执行.  就看MapperMethod.execute怎么转变为真正的SQL执行. 

MapperMethod

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  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;
      }

      // ... other case

    }
 }
}

阅读MapperMethod的源码,可以发现有其有2个成员变量 SqlCommand 和 MethodSignature. 

SqlCommand是对Mapper中Method映射的mapper statement(XML中的insert/update)的抽象. 

MethodSignature则是对Mapper Method的结构的抽象, 比如 xxx(@Param("userName") String userName, @Param("age") Integer age) . MethodSignature就是对Mapper-Method参数结构的抽象, 在使用的时候会转换为执行SQL需要的参数.   在书写mybatis相关的代码会出现的常见错误:  Parameter 'ew' not found. Available parameters are [pkid, param1] 就是在这里处理的,Object param = method.convertArgsToSqlCommandParam(args);会将Mapper-Method上声明的参数转换为Map(详细信息在这个方法里面已经比较清晰的描述了).

弄明白MapperMethod的结构之后, 就可以看execute里面完成了什么, 发现execute里面其实也就是将参数做了一下转换, 将Mapper Method name转换为xml中statement的ID; 具体的执行操作其实还是委托给SqlSession.insert/update/delete/select来完成的. 

SqlSession#insert/update/delete/select

前文中, MapperMethod将SQL的执行委托给SqlSession执行, 这里就以update操作为例来继续深入, 在DefaultSqlSession的实现中,可以看到insert/delete其实也是转换为update操作来执行的. 

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

statement: 这里的statement就是前文中SqlCommand中转换出来的statement-id.  可以在方法体里面通过这个statement-id去找对应的MappedStatement.

parameter: 也是前文中MethodSignature转换出来的参数(多个参数的时候是Map , 只有一个参数的时候就是参数本身的Value). 

(?为什么这里只考虑DefaultSqlSession的实现, 还得继续看下面SqlSession&SqlSessionFactory章节)

在SqlSession#update里面,可以看到通过前面的statement-id找到对应的MappedStatement, 然后将其交给executor去执行. 这里的executor就是mybatis的执行器了(SimpleExecutor & BatchExecutor等)

Executor

BaseExecutor#update

 @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

SimpleExecutor#doupdate

@Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

SimpleExecutor#preStatement

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

可以看到在SimpleExecutor中,依然不是具体的执行的地方, 在SimpleExecutor中只是对Connection做了处理. 真正的之心操作还是交给了StatementHandler来执行. 

既然操作都是交给StatementHandler来执行,那么看看StatementHandler是怎么得到的?

StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);

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;
  }

Note: 这里的interceptorChain.pluginAll其实就是支持的mybatis拦截器机制.

RoutingStatementHandler

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        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());
    }

  }

可以看到最终得到的是一个RoutingStatementHandler实例, 不过RoutingSatementHandler其实也只将操作委托,在构造的时候会根据MappedStaement在xml中对应的类型(STATEMENT/PREPARED/CALLABLE) 构造一个委托对象StatementHandler.  

在mapper.xml中, insert/select/update/select默认的statement类型都是STATEMENT.   可以通过标签上的statementType类型进行自定义. 比如下面就定义了CALLABLE 的statement

<insert id="test" parameterType="com.xxxxxx.modules.vo.sz.test" statementType="CALLABLE">
        {call app_create_check_file(
        #{id,mode=INOUT,jdbcType=VARCHAR},
        #{serviceVersion,mode=IN,jdbcType=VARCHAR}
        )}
</insert>

所以上面在通常情况下,委托的StatementHandler都是SimpleStatementHandler.

SimpleStatementHandler

StatementHandler在SimpleExecutor#doUpdate中被调用的方法分别是:

  • prepare

  • parameterize

  • update

1. prepare:  得到JDBC API中的Statement

BaseStaementHandler#prepare

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      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);
    }
  }

SimpleExecutor#instantiaStatement

 @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() != null) {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.createStatement();
    }
  }

可以看到也是通过Conection.createStatement来得到的. 和JDBC操作数据库的时候是一样的. 

2. parameterize

在SimpleStatementHandler中是空实现, 不需要. 

在CallableStatementHandler和PreparedStatementHandler中需要进行实现. 

3. update

这里终于进入到SQL的执行阶段了.

SimpleStatementHandler#update

  @Override
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }

可以看到这里拿着前面prepare创建的Statement进行SQL的执行操作: statement.execute(sql). 这里也和JDBC API操作数据库的时候一样. 

那么这里的Sql是哪里得到的, 回过头看Sql实例化的地方:

 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);
  }

可以看到boundSql是在StatementHandler构建的时候通过MappedStatement#getBoundSql(parameterObject)得到的. (这里就是Statement.execute真正执行的sql了) 

具体怎么通过MappedStaement#getBoundSql(parameterObject)获取到真正执行的SQL语句, 就要看MappedStatement是怎么实现的了.

到这里,Mapper-Method怎么转换成SQL执行的过程就明白了, 下面需要只需要弄清楚怎么得到SQL语句就行了.

MappedStatement#getBoundSql

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();
        }
      }
    }

可以看到这里#getBoundSql只是对从mapper.xml获取到的原始sql进行加工, 将# { } 或者${}参数变为实际方法执行的参数. 

真正原始的SQL还是通过sqlSource.getBoundSql得到的, 还得看sqlSource怎么得到的.

回溯sqlSource被赋值的地方, 可以看到是在MappedStatement.Builder中被赋值的, 查看Buidler被调用的地方. (只发现唯一一处地方, MapperBuilderAssisant#addMappedStatement)

可以看到MapperBuilderAssisant#addMappedStatementXMLStatementBuilderMapperAnnotationBuilder都有被调用.  很显然,这2个类分别是用来解析XML和注解方式的SQL的.

XMLStatementBuilder可以看到使用LangDriver#createSqlSource得到XML中SQL对应的SqlSource对象.

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

进入XMLLanguageDriver的实现, 可以看到就是对mapper.xml文件的节点的读取.

 @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

到这里, 怎么得到mapper.xml中的原始SQL的地方也明白了. (ps:具体的xml解析就不继深入了)

SqlSession & SqlSessionFactory

在前文中知道了,Spring容器中Mapper实例的获取是交给SqlSession#getMapper来完成的. 

还有Mapper-Method的执行也是委托给SqlSession#select/update/delete/insert等操作的, 然后再由SqlSession委托给mybatis Executor来执行. 

在SqlSession & SqlSessionFactory体系中有这么多实现, 那么在具体使用中是使用的那个实现?  这个一般都是在Spring集成Mybatis的时候配置的.  下面是项目中使用的配置:

    @Bean("sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));
        return bean.getObject();
    }

    @Bean("sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

所以这里的SqlSession使用的也是SqlSessionTemplate

SqlSessionTemplate

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }


可以看到SqlSessionTemplate是将selectOne/update/delete/insert操作委托给sqlSessionProxy变量完成的. 

sqlSessionProxy是通过Jdk动态代理得到的一个代理实例. 具体的selectOne/update/delete/insert等行为还是得看InvocationHandler.invoke代理的实现逻辑.

SqlSessionInteceptor

 private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

可以看到在代理实现逻辑里面,也是通过获取到一个SqlSession对象, 然后反射执行对应的方法.  

 SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);

Object result = method.invoke(sqlSession, args);

那么是怎么从哪里得到的这个用来执行的SqlSession对象?又是对应的那个类的实例?

#getSqlSession

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

可以看到这里是从TransactionSynchronizationManager里面以SqlSessionFactory作为key来获取到SqlSessionHolder, 从而从里面获取到SqlSession

TransactionSynchronizaationManager其实就是用来管理事务相关对象的线程上下文.  会将上线文中事务管理用到的JDBC Connection以及这里Mybatis用到的SqlSession放入到ThreadLocal中. 

可以看到#getSqlSession中如果没有TransactionSynchronizationManager在中获取到SqlSession. 则通过SqlsessionFactory构建一个SqlSession对象, 并将其注册到上下文中. 在执行下一个SQL语句的时候就能够从TransactionSynchronizationManager获取创建的SqlSession对象了。

那么这里用来创建SqlSession的SqlSessionFactory又是从哪里得到的?是那个类的对象?

DefaultSqlSessionFactory

前面SqlSession&SqlSessionFactory配置中可以看到SqlSessionFactory是通过SqlSessonFactoryBean#getObject得到的.  这里也是Spring FactoryBean<T>的一处应用.

SqlSessionFactoryBean#getObject

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

查看赋值的地方afterPropertiesSet

 @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

#bulildSqlSessionFactory

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

// 前面的大部分代码忽略
return this.sqlSessionFactoryBuilder.build(configuration);}

可以看到SqlSession是通过SqlSessionFactorBuilder构建出来的.

SqlSessionFactoryBuilder#build

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

可以看到这里最终得到的SqlSessionFactory是一个DefaultSqlSessionFactory实例。 

那么继续探索DefaultSqlSessionFactory#openSession就可以得到的SqlSession是什么了.

DefaultSqlSessionFactory#openSession

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

#openSessionFromDataSource

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  

可以看到这里得到的最终执行的SqlSession实例对象是DefaultSqlSession的实例. 

事务Transaction

【Spring】如何完成事务传播特性中,通过了解TransactionInterceptor知道, 也是通过TransactionSynchronizationManager来维护的上下文的JDBC Connection. 那么在SqlSession中执行SQL的JDBC Connection是如何与TransactionInterceptor中的JDBC Connection关联的. 

SimpleExecutor#prepareStatement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

BaseExecutor#getConnection

 protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

在前面SimpleExecutor中执行SQL语句的时候,这里可以看到数据库执行操作的Connection是从Tranasaction对象中获取的. Transacion是Mybatis对数据库事务的抽象, 区别于Spring中的事务抽象. 

要想知道Connection是从哪里来的,就得看这个Transaction对象是从哪里来的了?

BaseExecutor

 protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

可以看到Transaction是在Mybatis Executor构造的时候传递过来的, 还得继续向上追溯. 

这里追溯到构建SqlSession的时候,会同时创建Excecutor对象,也会同时得到一个Transaction对象.  即前文中的如何得到的SqlSession对象代码:

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

可以看到这里的Transaction是从TransactionFactory中获取到的, TransactionFactory也有好几个实现, 那么具体是那个实现了?

  • JdbcTransactionFactory

  • ManagedTransactionFactory

  • SpringManagedTransactionFactory

跟踪#getTransactionFactoryFromEnvironment代码, 可以看到TransactionFactory是从Environment环境变量中获取的.

#getTransactionFactoryFromEnvironment

  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

那么这个environment.getTransactionFactory中的TransactionFactory又是在哪里被赋值的了?

最后追踪到SqlSessionFactoryBean#buildSqlSessionFactory中:

#buildSqlSessionFactory

   if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

找到如上的代码片段,最终得到DefaultSqlSession中构造的时候使用的TransactionFactory就是SpringManagedTransactionFactory

SpringManagedTransactionFactory

找到了SpringManagedTransactionFactory,继续看它是怎么得到Mybatis的Transaction对象的. 

public class SpringManagedTransactionFactory implements TransactionFactory {

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(Connection conn) {
    throw new UnsupportedOperationException("New Spring transactions require a DataSource");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setProperties(Properties props) {
    // not needed in this version
  }

}

可以看到这里直接就通过数据库DataSource构建了一个SpringManagedTransaction.  这里得到Mybatis-Transaction的代码比较简单容易理解, 下面就继续看看这个SpringManagedTransaction是如何获取JDBC Connection的.

SpringMangedTranaction#getConnection

  @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

#openConnection

  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(
          "JDBC Connection ["
              + this.connection
              + "] will"
              + (this.isConnectionTransactional ? " " : " not ")
              + "be managed by Spring");
    }
  }

openConenction可以看到Connection是通过DataSourceUtils.getConnectoin(datasource)方式获取到的.

深入DatasourceUtils#getConenction

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
		try {
			return doGetConnection(dataSource);
		}
		catch (SQLException ex) {
			throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
		}
		catch (IllegalStateException ex) {
			throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
		}
	}

DataSourceUtils#doGetConenction

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
		Assert.notNull(dataSource, "No DataSource specified");

		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
		if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
			conHolder.requested();
			if (!conHolder.hasConnection()) {
				logger.debug("Fetching resumed JDBC Connection from DataSource");
				conHolder.setConnection(fetchConnection(dataSource));
			}
			return conHolder.getConnection();
		}
		// Else we either got no holder or an empty thread-bound holder here.

		logger.debug("Fetching JDBC Connection from DataSource");
		Connection con = fetchConnection(dataSource);

		if (TransactionSynchronizationManager.isSynchronizationActive()) {
			try {
				// Use same Connection for further JDBC actions within the transaction.
				// Thread-bound object will get removed by synchronization at transaction completion.
				ConnectionHolder holderToUse = conHolder;
				if (holderToUse == null) {
					holderToUse = new ConnectionHolder(con);
				}
				else {
					holderToUse.setConnection(con);
				}
				holderToUse.requested();
				TransactionSynchronizationManager.registerSynchronization(
						new ConnectionSynchronization(holderToUse, dataSource));
				holderToUse.setSynchronizedWithTransaction(true);
				if (holderToUse != conHolder) {
					TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
				}
			}
			catch (RuntimeException ex) {
				// Unexpected exception from external delegation call -> close Connection and rethrow.
				releaseConnection(con, dataSource);
				throw ex;
			}
		}

		return con;
	}

可以看到这里也是使用TransactionSynchronizationManager来管理的JDBC Connection, 和TransactionInterceptor中呼应上了.  他们是使用的一套体系来管理的上下文中的Connection.