mybatis 源码分析

MyBatis源码分析

1. Mybatis的初始化

  在利用spring配置mybatis时,需要做出如下配置,mybatis初始化时便利用如下配置生成SqlSessionFactory,SqlSessionTemplate等实例。


<!--数据源信息-->
<bean id="testSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/testdatabase"/>
    <property name="username" value="root"/>
    <property name="password" value="********"/>
    <property name="initialSize" value="5"/>
    <property name="maxIdle" value="1800"/>
    <property name="maxWaitMillis" value="1000"/>
</bean>

<!--mybatis相关设置包括数据源,mapper文件路径,mapper文件所对应的dao路径-->
<bean id="sqlSessionFactory" name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--dataource-->
    <property name="dataSource" ref="testSource"/>
    <!--Mapper files-->
    <property name="mapperLocations" value="classpath:test-sqlmap/*Mapper.xml"/>
    <property name="typeAliasesPackage" value="com.hewenkai.controller.bean"/>
</bean>

<!--mybatis所映射的mapper接口路径-->
<bean id="mapperScannerConfiguration" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.hewenkai.controller.mapper"/><!-- 多个使用逗号分隔 -->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
1.1 SqlSessionFactory

  SqlSessionFactory为工厂类,负责在进行具体的sql语句执行时生成对应的SqlSession对象。每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。在具体执行sql语句时,mybatis会为每个执行过程通过SqlSessionFactory生成一个SqlSession去具体执行sql。

1.2 SqlSessionTemplate

  SqlSessionTemplate 是 MyBatis-Spring 的核心,SqlSessionTemplate完全负责SqlSession的创建,执行以及关闭等生命周期管理,其利用SqlSessionFactory进行SqlSession的创建,可以看到SqlSessionTemplate实现了SqlSession接口。

1.3 MapperProxy

  在初始化阶段,会针对每个mapper接口生成一个代理,在获取接口所对应的实例时 实际上获取的便是此代理,通过此代理来进行真正的执行,在代码里可以看到,myBatis是利用的动态代理机制进行的,调用mapper接口的方法时实际调用的是MapperProxy的invoke方法。

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

2. SqlSession的生成

2.1 MapperProxy

  上文说过,在使用mapper接口的方法时,实际上是调用的MapperProxy的invoke方法,其代码如下:

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 Exception Util.unwrapThrowable(t);
  }
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

  可以看到,如果调用method的不是接口而是个实例类时,直接调用实例的method方法,在很久之前常常会这么使用MyBatis,现在一般不会这么使用了。最后生成了,MapperMethod实例,当mapper方法被调用的时候对应的MapperProxy会生成相应的MapperMethod并且会缓存起来,这样当多次调用同一个mapper方法时候只会生成一个MapperMethod,提高了时间和内存效率。在调用MapperMethod的execute方法时,会根据sql语句的类型去调用不同的方法。

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

  进一步分析可知,最后是调用了MapperMethod域中的SqlSession去具体执行,而SqlSession又是从什么地方传过来的呢?再往上翻源码可知,此SqlSession是从MapperProxy中传递过来的,通过调试可知此SqlSession为SqlSessionTemplate中调用getConfiguration().getMapper(type, this);将自身传递过去,前文中也说过,SqlSessionTemplate也实现了SqlSession接口,此时在这边用上了。在其实现SqlSession接口时,都是将调用转发到其成员变量sqlSessionProxy上,从构造函数中可以看出,sqlSessionProxy也是一个生成的一个动态代理,最终将会把调用转发到SqlSessionInterceptor的invoke方法中。

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

  此时终于能够生成最后能够真正执行的SqlSession,此时mybatis将SqlSession的获取交由Spring的事务管理,将会保证使用的 SqlSession 与当前 Spring 的事务相关,如果当前spring事务中存在SqlSession则直接获取SqlSession,否则生成一个新的SqlSession并将其注册到spring事务中,因此可以保证了SqlSessionTemplate的线程安全性,从而也知道非事务环境下,每次操作数据库都使用新的sqlSession对象。

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

3. SqlSession的执行

  历经这么多,终于生成了最终的SqlSession实例,其一般是DefaultSqlSession类型,具体执行过程如下(以selectList为例):

@Override
public <E> List<E> selectList(String statement) {
  return this.selectList(statement, null);
}

@Override
public <E> List<E> selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public <E> List<E> 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();
  }
}
3.1 MappedStatement

  首先生成了一个MappedStatement实例,MappedStatement维护一条<select|update|delete|insert>节点的封装,即代表mapper文件中的一个sql语句,其中封装了SqlSource,负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,BoundSql表示动态生成的SQL语句以及相应的参数信息。

3.2 Executor

  Executor是MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护。

3.2.1 BaseExecutor

  BaseExecutor是抽像类,在创建时会根据传过来的ExecutorType创建不同的类,如下:

  • SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
  • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)
  • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
3.2.2 CachingExecutor

  先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。

3.2.3 DefaultSqlSession中executor执行过程

  生成Executor时,如果允许缓存,则生成CachingExecutor,其内部封装了具体的BaseExecutor,进行查询时,首先会调用CachingExecutor的query方法,此方法首先利用MappedStatement生成BoundSql,然后利用相关信息生成缓存key,再根据此key从缓存中获取结果,如果缓存中不存在再调用相应的BaseExecutor进行实际的查询过程。否则直接根据ExecutorType生成具体的BaseExecutor。

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
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) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

  实际查询时调用BaseExecutor的query接口,之后调用到queryFromDatabase接口,最终调用doQuery接口。在doQuery接口中使用了StatementHandler以及ResultHandler

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
3.3 StatementHandler

  StatementHandler负责处理Mybatis与JDBC之间Statement的交互,而JDBC中的Statement负责与数据库进行交互的对象。StatementHandler接口的实现大致有四个,其中三个实现类都是和JDBC中的Statement响对应的:

  • SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理;
  • PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口;
  • CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口;
  • RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用。

  在doQuery接口中创建的是RoutingStatementHandler,而RoutingStatementHandler则根据传入的mappedStatement.getStatementType创建具体类型的StatementHandler,作为自己的delegate成员,在执行StatementHandler.doQuery时将其直接转发到delegate上。回到Executor.doQuery方法上,在创建了StatementHandler后,又在此基础上生成了Statement对象,这个对象我们比较熟悉,是JDBC用来执行Sql语句的对象,将其传入StatementHandler.doQuery接口。此时以SimpleStatementHandler为例,就是直接调用Statement.execute方法。

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

}
/**SimpleStatementHandler**/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  String sql = boundSql.getSql();
  statement.execute(sql);
  return resultSetHandler.<E>handleResultSets(statement);
}
3.4 ResultSetHandler

  ResultSetHandler比较简单,就是将Statement实例执行之后返回的ResultSet结果集转换成我们需要的类型形式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值