Spring环境下Mybatis的配置以及工作机制

MyBatis 是一款一流的支持自定义SQL、存储过程和高级映射的持久化框架。MyBatis 能够使用简单的XML 格式或者注解进行来配置,能够映射基本数据元素、Map 接口和POJO(普通java 对象)到数据库中的记录。所有的MyBatis 应用都以SqlSessionFactory 实例为中心。SqlSessionFactory 实例通过SqlSessionFactoryBuilder 来获得,SqlSessionFactoryBuilder 能够从XML 配置文件或者通过自定义编写的配置类(Configuration class),来创建一个SqlSessionFactory 实例。在非Spring环境下XML文件的配置如下

<configuration>
  <environments default="development">
     <environment id="development">
     <transactionManager type="JDBC"/>
     <dataSource type="POOLED">
 <span style="white-space:pre">	</span>  <property name="driver" value="${driver}"/>
          <property name="url" value="${url}"/>
          <property name="username" value="${username}"/>
<span style="white-space:pre">	</span>  <property name="password" value="${password}"/>
     </dataSource>
     </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>
上面为Mybatis配置了事务管理器以及数据源(数据库连接池)。一般在Spring环境下数据源以及事务管理器不需要在<configuration />文件中配置,而是借助于mybatis-spring包下的org.mybatis.spring.SqlSessionFactoryBean来注入第三方数据源。mybatis在Spring的 applicationContext.xml 中的配置如下

<!-- 配置mybatis -->
<bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="mDataSource" />
	<property name="configLocation"
		value="classpath:/mysql-mapper/Configuration.xml" />
	<property name="mapperLocations" value="classpath:/mysql-mapper/*Mapper.xml" />
</bean>
	
<!-- 数据源配置, 使用应用中的DBCP数据库连接池 -->
<bean id="mDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
	<!-- Connection Info -->
	<property name="driverClassName" value="${jdbc.driver}" />
	<property name="url" value="${jdbc.url}" />
	<property name="username" value="${jdbc.username}" />
	<property name="password" value="${jdbc.password}" />

	<!-- Connection Pooling Info -->
	<property name="maxActive" value="${dbcp.maxActive}" />
	<property name="maxIdle" value="${dbcp.maxIdle}" />
	<property name="defaultAutoCommit" value="false" />
	<!-- 连接Idle一个小时后超时 -->
	<property name="timeBetweenEvictionRunsMillis" value="3600000" />
	<property name="minEvictableIdleTimeMillis" value="3600000" />
</bean>
<!-- mybatis Dao -->
<bean id="mBaseDao" class="com.rfidMidware.dao.BaseDao">
	<property name="sqlSessionFactory" ref="mSqlSessionFactory" />
</bean>
由上面的配置可以看出,SqlSessionFactoryBean中注入了第三方dataSource,即dbcp数据源。同时在配置中给出了mybatis配置文件以及Mapper映射文件的路径。这时我们来看看Spring环境下的mybatis配置文件是怎么样的

<configuration>
	<properties resource="jdbc.properties" />
	<settings>
		<setting name="cacheEnabled" value="true" />
		<setting name="lazyLoadingEnabled" value="false" />
	</settings>
	<typeAliases>
		<typeAlias type="com.rfidMidware.model.controlManager.Manager" alias="Manager" />
	</typeAliases>
</configuration>
观察上面的配置,可以看出已经不需要再配置数据源、事务管理器等选项了。顺便说说上面的配置吧,<properties />文件给出了外部属性文件。<settings />很重要,用于改变运行中mybatis的行为。可以看出我开了mybatis的二级缓存。我们知道Mybatis共分两级缓存(不知道的话可以点开链接哟,个人觉得很好的博客的链接)。 一级缓存基于Session ,二级缓存作用域为Mapper的范围。但是由于Spring环境下SqlSession是由Spring容器管理的,mybatis的一级缓存不再有效。<typeAlias />为类全名产生一个简短的别名,方便Mapper文件使用。
配置好了,那么mybatis具体是如何工作的呢,首先,看一下我们在业务层读写数据库时使用的mybatis的接口吧(这只是使用mybatis的一种方式)

public class BaseDao  extends SqlSessionDaoSupport {

	public int  save(String key, Object object){
		return getSqlSession().insert(key, object);
	}
	
	public int update(String key, Object object){
		return getSqlSession().update(key, object);
	}
	
	public int  update(String key){
		return getSqlSession().update(key);
	}
	
	public int  delete(String key, Serializable id) {
		return getSqlSession().delete(key, id);
	}.................
这里使用的是继承SqlSessionDaoSupport的方法。来看看这个类

public abstract class SqlSessionDaoSupport extends DaoSupport {

	  private SqlSession sqlSession;

	  private boolean externalSqlSession;

	  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
	    if (!this.externalSqlSession) {
	      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); 	    }
	  }

	  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
	    this.sqlSession = sqlSessionTemplate;
	    this.externalSqlSession = true;
	  }

	  /**
	   * Users should use this method to get a SqlSession to call its statement methods
	   * This is SqlSession is managed by spring. Users should not commit/rollback/close it
	   * because it will be automatically done.
	   *
	   * @return Spring managed thread safe SqlSession
	   */
	  public SqlSession getSqlSession() {
	    return this.sqlSession;
	  }
	}
当我们使用BaseDao接口,调用getSqlSession时会调用其基类SqlSessionDaoSupport相应的方法。而这个SqlSession是其子类SqlSessionTemplate 的一个实例。

this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); 

而在上面的配置文件中有这一句 <property name="sqlSessionFactory" ref="mSqlSessionFactory" />   ,由此处可以看出上句SqlSessionTemplate实例化传入的参数sqlSessionFactory即 org.mybatis.spring.SqlSessionFactoryBean。 

先来看看SqlSessionTemplate的真面目

public class SqlSessionTemplate implements SqlSession {

	  private final SqlSessionFactory sqlSessionFactory;

	  private final ExecutorType executorType;

	  private final SqlSession sqlSessionProxy;

	  private final PersistenceExceptionTranslator exceptionTranslator;

	  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());			
		} .......
最重要的是最后一句,使用java的动态代理生成的 SqlSessionProxy来执行具体的 数据库CRUD,JDK的动态代理是基于接口的。该代理实现SqlSession的接口,并使用SqlSessionIntercepter拦截器将mybatis方法引导到正确的由Spring事务管理器产生的SqlSession中去。我们知道动态代理中,当代理类调用相应的类方法时候会调用InvocationHandler中的invoke方法。而SqlSessionIntercepter是InvocationHandler的子类,这个拦截器中最重要的的是invoke方法

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      }

拦截器中getSession方法调用org.mybatis.spring.SqlSessionUtils中的 静态 getSession方法;我们再观察这个方法(已精简)

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

    //需同步时用holder来管理sqlSession
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    if (holder != null && holder.isSynchronizedWithTransaction()) {
      if (holder.getExecutorType() != executorType) {
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
      }
      holder.requested();

      return holder.getSqlSession();
    }
   SqlSession session = sessionFactory.openSession(executorType);
    ................
    return session;
  }
注意到这一句   SqlSession session = sessionFactory. openSession (executorType);  调用传递过来的sessionFactory来打开一个sqlSession。上文说到Spring注入的sqlSessionFactory是 org.mybatis.spring.SqlSessionFactoryBean。  SqlSessionFactoryBean这个类是用来生成SqlSessionFactory的,这也是一般情况下在Spring环境下生成一个共享的SqlSessionFactory的方法。实际上依赖注入时向基于mybatis的DAO接口(此例中即为BaseDao)注入的是其内部生成的SqlSessionFactory

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

			  private static final Log logger = LogFactory.getLog(SqlSessionFactoryBean.class);

			  private Resource configLocation;

			  private Resource[] mapperLocations;

			  private DataSource dataSource;

			  private TransactionFactory transactionFactory;

			  private Properties configurationProperties;

			  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

			  private SqlSessionFactory sqlSessionFactory;

			  private String environment = SqlSessionFactoryBean.class.getSimpleName(); // EnvironmentAware requires spring 3.1

; .............}
可以看出SqlSessionFactoryBean中包含了dataSource、sqlSessionFactoryBuilder、sqlSessionFactory以及配置文件Configuration等等的实例,该类使用

this.sqlSessionFactory = buildSqlSessionFactory(); 

而buildSqlSessionFactory又调用 this.sqlSessionFactoryBuilder.build(configuration);

再来看看这个build方法

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
联系上文可知为BaseDao注入的SqlSessionFactory即为此处的 DefaultSqlSessionFactroy,同时传入配置文件Configuration的实例作为参数。所以上文调用openSession有两种方法 openSessionFromDataSource 和 openSessionFromConnection

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      Transaction tx = null;
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);  //<strong><span style="color:#ff0000;">这是实际执行SQL语句的执行器</span></strong>
      return new DefaultSqlSession(configuration, executor, autoCommit);  //<span style="color:#ff0000;"><strong>可知 这就是我们所用的SqlSession的本来面目</strong></span>
   
  }
public SqlSession openSession(Connection connection) {
    return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
  }
由上可知,我们实际上使用的SqlSession即为其子类 DefaultSqlSession.

public class DefaultSqlSession implements SqlSession {

  private Configuration configuration;
  private Executor executor;

  private boolean autoCommit;
  private boolean dirty;
  
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }...............................
DefaultSqlSession实现了SqlSession的所有接口,仔细查看源码可以明显看出 实际上负责执行SQL语句的是Executor。

Executor的生成,则是通过 org.apache.ibatis.session.Configuration的 newExecutor方法生成。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
	    executorType = executorType == null ? defaultExecutorType : executorType;
	    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
	    Executor executor;
	    if (ExecutorType.BATCH == executorType) {
	      executor = new BatchExecutor(this, transaction);
	    } else if (ExecutorType.REUSE == executorType) {
	      executor = new ReuseExecutor(this, transaction);
	    } else {
	      executor = new SimpleExecutor(this, transaction);
	    }
	    if (cacheEnabled) {
	      executor = new CachingExecutor(executor);
	    }
	    executor = (Executor) interceptorChain.pluginAll(executor);
	    return executor;
	  }
可以看出,如果不开启cache的话,创建的Executor只是3中基础类型之一,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。开启cache的话(默认是开启的并且没有任何理由去关闭它),就会创建CachingExecutor,它以前面创建的Executor作为唯一参数。CachingExecutor在查询数据库前先查找缓存,若没找到的话调用delegate(就是构造时传入的Executor对象)从数据库查询,并将查询结果存入缓存中。Executor对象是可以被插件拦截的,如果定义了针对Executor类型的插件,最终生成的Executor对象是被各个插件插入后的代理对象。(mybatis的分页是基于内存的逻辑分页,数据量比较大时候往往会占用过多内存,一般提倡的分页方式是物理分页,这就需要自己通过拦截器来实现)。

public class SimpleExecutor extends BaseExecutor {

		  public SimpleExecutor(Configuration configuration, Transaction transaction) {
		    super(configuration, transaction);
		  }

		  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);
		    }
		  }
可以看出,Executor的具体工作都交由 StatementHandler来执行。而StatementHandler也是在Configuration中生成的。

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;
 }
可以看到每次创建的StatementHandler都是RoutingStatementHandler,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有 SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的。

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

		  }
StatementHandler创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等。在具体的 Executor中执行。代码如下:

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
	    Statement stmt;
	    Connection connection = getConnection(statementLog);
	    stmt = handler.prepare(connection);
	    handler.parameterize(stmt);
	    return stmt;
	  }
其中handler的参数化方法 此handler即PreparedStatementHandler 

public void parameterize(Statement statement) throws SQLException {
	    parameterHandler.setParameters((PreparedStatement) statement);
	  }
其中 parameterHandler 在 PreparedStatementHandler的基类中BaseStatementHandler
public abstract class BaseStatementHandler implements StatementHandler {

		  protected final Configuration configuration;
		  protected final ObjectFactory objectFactory;
		  protected final TypeHandlerRegistry typeHandlerRegistry;
		  protected final ResultSetHandler resultSetHandler;
		  protected final ParameterHandler parameterHandler;

		  protected final Executor executor;
		  protected final MappedStatement mappedStatement;
		  protected final RowBounds rowBounds;

		  protected BoundSql boundSql;
其中 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); 在Configuration中生成

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
		    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
		    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
		    return parameterHandler;
		  }
其中 mappedStatement.getLang()返回languageDriver
public class XMLLanguageDriver implements LanguageDriver {
 public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
   return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
 }
即生成 DefaultParameterHandler

public class DefaultParameterHandler implements ParameterHandler {

		  private final TypeHandlerRegistry typeHandlerRegistry;

		  private final MappedStatement mappedStatement;
		  private final Object parameterObject;
		  private BoundSql boundSql;
		  private Configuration configuration;

		  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
		    this.mappedStatement = mappedStatement;
		    this.configuration = mappedStatement.getConfiguration();
		    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
		    this.parameterObject = parameterObject;
		    this.boundSql = boundSql;
		  }

		  public Object getParameterObject() {
		    return parameterObject;
		  }

		  public void setParameters(PreparedStatement ps) throws SQLException {
		    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		    if (parameterMappings != null) {
		      for (int i = 0; i < parameterMappings.size(); i++) {
			ParameterMapping parameterMapping = parameterMappings.get(i);
			if (parameterMapping.getMode() != ParameterMode.OUT) {
			  Object value;
			  String propertyName = parameterMapping.getProperty();
			  if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
			    value = boundSql.getAdditionalParameter(propertyName);
			  } else if (parameterObject == null) {
			    value = null;
			  } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
			    value = parameterObject;
			  } else {
			    MetaObject metaObject = configuration.newMetaObject(parameterObject);
			    value = metaObject.getValue(propertyName);
			  }
			  TypeHandler typeHandler = parameterMapping.getTypeHandler();
			  JdbcType jdbcType = parameterMapping.getJdbcType();
			  if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
			  typeHandler.setParameter(ps, i + 1, value, jdbcType);
			}
		      }
		    }
		  }

		}
这里面最重要的一句其实就是最后一句代码,它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢,它又是如何决断出来的呢?BaseStatementHandler的构造方法有这么一句:
this.boundSql= mappedStatement.getBoundSql(parameterObject);
它触发了sql 的解析,在解析sql的过程中,TypeHandler也被决断出来了,决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如:参数类型是String的话就用StringTypeHandler,参数类型是整数的话就用IntegerTypeHandler等。
参数设置完毕后,执行数据库操作(update或query)。如果是query最后还有个查询结果的处理过程。

如果再结合Mybatis几个重要类来分析一下具体Sql语句的生成过程,了解一下MappedStatement、SqlSource等类 相信Mybatis的实现机制应该就比较清楚了。先写到这里了,如果有疏漏错误处,多多指出啊。

























猜你喜欢

转载自blog.csdn.net/quanzhongzhao/article/details/45562043
今日推荐