spring如何管理mybatis(一) ----- 動態代理接口


問題來源

  最近在集成spring和mybatis時遇到了很多問題,從網上查了也解決了,但是就是心里有點別扭,想看看到底怎么回事,所以跟了下源碼,終於發現了其中的奧妙。

    問題分析

        首先我們來看看基本的配置。

  spring的配置:

     <!-- 數據庫配置 -->
     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${db.driver}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.userName}"/>
        <property name="password" value="${db.password}"/>
        <property name="maxActive" value="${druid.maxActive}"></property>
        <property name="maxWait" value="${druid.maxWait}"></property>
    </bean>
       
    <bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--數據庫連接池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 加載mybatis mapper文件的配置 -->
        <property name="mapperLocations" value="classpath*:mapper/*.xml"/>
    </bean>
    <!-- sqlSession不是必選項 -->
    <!-- <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="mSqlSessionFactory"/>
    </bean> -->
     <!--動態代理實現 不用寫dao的實現 -->  
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
          <property name="basePackage" value="com.zex.dao" />
          <property name="sqlSessionFactoryBeanName" value="mSqlSessionFactory"></property>
    </bean>
        <!-- 事務管理 -->
    <bean id="transactionManagermeta"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 事務注解支持 -->
    <tx:annotation-driven/>

mapper文件和dao接口

  

     controller層代碼

         源碼跟蹤

     首先我們分解下spring-mybatis配置信息,數據庫配置不說了,我們來看看sqlSessionFactory的配置

 <bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--數據庫連接池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 加載mybatis mapper文件的配置 -->
        <property name="mapperLocations" value="classpath*:mapper/*.xml"/>
    </bean>

    這個配置,主要是把SqlSessionFactoryBean用spring管理起來了,我們一起來看看這個bean的作用

/**
 * {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}.
 * This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context;
 * the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection.
 *
 * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction
 * demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions
 * which span multiple databases or when container managed transactions (CMT) are being used.
 */

  這是這個類的注釋:這個類主要用來創建Mybatis需要的SqlSessionFactory,在spring的上下文共享這個類。這里可以看出這個類用來

管理mybatis的配置信息,講mybatis的信息管理載spring中,我們看下基本屬性。

  

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

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

  private Resource configLocation;

  private Configuration configuration;

  private Resource[] mapperLocations;

  private DataSource dataSource;

  private TransactionFactory transactionFactory;

  private Properties configurationProperties;

  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

  private SqlSessionFactory sqlSessionFactory;

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

  private boolean failFast;

  private Interceptor[] plugins;

  private TypeHandler<?>[] typeHandlers;

  private String typeHandlersPackage;

  private Class<?>[] typeAliases;

  private String typeAliasesPackage;

  private Class<?> typeAliasesSuperType;

  //issue #19. No default provider.
  private DatabaseIdProvider databaseIdProvider;

  private Class<? extends VFS> vfs;

  private Cache cache;

  private ObjectFactory objectFactory;

  private ObjectWrapperFactory objectWrapperFactory;
}

這里我們看到了有個

  private Resource configLocation;

 這個屬性用來管理mybatis基本配置信息的xml的位置,sqlSessionFactoryBean會根據這個配置加載Configuration,當然我們也可以通過這個類中

其他的參數來配置,例如typeHandler,typeAliasesPackages等等,這些既可以在Configuration的xml中配置,也可以直接配置。所以這個bean主要作用就是生成configuration,

然后通過sqlSessionFactoryBuilder來創建sqlSessionFactory,可以說最重要的就是創建這個sqlSessionFactory。

  接下來我們看看SqlSessionTemplate

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="mSqlSessionFactory"/>
    </bean>
SqlSessionTemplate這個類實現了mybatis的sqlSession,mybatis的sqlSession主要是執行數據庫操作,spring實現了SqlSessionTemplate這個類,主要是講mybatis對數據庫的
操作轉嫁到spring中來,讓spring來進行數據的操作。我們看看這個類的屬性。
public class SqlSessionTemplate implements SqlSession {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

  /**
   * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
   * provided as an argument.
   *
   * @param sqlSessionFactory
   */
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  }

  /**
   * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
   * provided as an argument and the given {@code ExecutorType}
   * {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate}
   * is constructed.
   *
   * @param sqlSessionFactory
   * @param executorType
   */
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(
            sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  }

  /**
   * Constructs a Spring managed {@code SqlSession} with the given
   * {@code SqlSessionFactory} and {@code ExecutorType}.
   * A custom {@code SQLExceptionTranslator} can be provided as an
   * argument so any {@code PersistenceException} thrown by MyBatis
   * can be custom translated to a {@code RuntimeException}
   * The {@code SQLExceptionTranslator} can also be null and thus no
   * exception translation will be done and MyBatis exceptions will be
   * thrown
   *
   * @param sqlSessionFactory
   * @param executorType
   * @param 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());
  }

  這個類包含有四個基本的屬性,其中的SqlSessionFactory就是我們之前通過SqlSessionFactoryBean生成的那個SqlSessionFactory,他的作用是提供Configation,

另一個重要的屬性就是SqlSessionProxy這個類其實是個代理類,代理的Mybatis的sqlSession接口,這樣他就可以擁有MyBatis的sqlSession的所有方法了。這個代理類在執行的時候

其實是走的

SqlSessionInterceptor的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final 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) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }

 這個方法就是獲取真實的sqlSession然后調用數據庫的操作。

ok,以上就是spring和mybatis的整合點,接下來我們看看是如何只通過一個接口就能操作數據庫的,肯定用的是代理模式,只是mybatis用的太好了。

  首先我們來看個類,MapperFactoryBean

/**
 * BeanFactory that enables injection of MyBatis mapper interfaces. It can be set up with a
 * SqlSessionFactory or a pre-configured SqlSessionTemplate.
 * <p>
 * Sample configuration:
 *
 * <pre class="code">
 * {@code
 *   <bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
 *     <property name="sqlSessionFactory" ref="sqlSessionFactory" />
 *   </bean>
 *
 *   <bean id="oneMapper" parent="baseMapper">
 *     <property name="mapperInterface" value="my.package.MyMapperInterface" />
 *   </bean>
 *
 *   <bean id="anotherMapper" parent="baseMapper">
 *     <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" />
 *   </bean>
 * }
 * </pre>
 * <p>
 * Note that this factory can only inject <em>interfaces</em>, not concrete classes.
 *
 * @author Eduardo Macarron
 *
 * @see SqlSessionTemplate
 */
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }/**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }


}

   這個類就是可以注入mapper接口的工廠類,可以理解為他可以通過接口生產一個代理類用來調用接口的工作,首先他是個FactoryBean可以通過getObject(),獲取到他管理的bean,

這個類最主要的就是傳入一個sqlSessionFactory。

我們先來看看一般的用法

<bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
 *     <property name="sqlSessionFactory" ref="sqlSessionFactory" />
 *   </bean>
 *
 *   <bean id="oneMapper" parent="baseMapper">
 *     <property name="mapperInterface" value="my.package.MyMapperInterface" />
 *   </bean>
 *
 *   <bean id="anotherMapper" parent="baseMapper">
 *     <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" />
 *   </bean>

  我們看到將這個類由spring管理,然后注入sqlSessionFactory,然后又用其他的類去繼承它並注入接口,這樣這個接口就被管理起來了,生成了代理類。我們在獲取這個接口的時候得到的其實就是代理類。不過這樣子有點麻煩,我們每次都要進行接口的配置,所以spring提供了org.mybatis.spring.mapper.MapperScannerConfigurer這個類來管理所有的接口了,這個類會所有所有的配置的包中的接口,然后將每個接口的定義設置好生成代理相應的信息。

 @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

        if (logger.isDebugEnabled()) {
          logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(MapperFactoryBean.class);

        definition.getPropertyValues().add("addToConfig", this.addToConfig);

        boolean explicitFactoryUsed = false;
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
          definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionFactory != null) {
          definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
          explicitFactoryUsed = true;
        }

        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }

        if (!explicitFactoryUsed) {
          if (logger.isDebugEnabled()) {
            logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
          }
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
      }
    }

    return beanDefinitions;
  }

我們重點看下

 definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
 definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("addToConfig", this.addToConfig);

 這三行代碼,指定了mapper接口的類型--MapperFactoryBean,以及相應的接口信息,這樣bean的定義就指定了必要的信息,當spring創建這個mapper接口對應的bean的時候就會生成相應的MapperFactoryBean類,當需要接口實例時就會調用MapperFactoryBean的getObject()方法獲取相應的bean。

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

這個方法就是獲取mapper接口對應的代理類,這個方法給我們上了一堂如何完美利用jdk代理類的課。建議大家可以研究下。這里不是我們的重點,就不帶大家研讀了。

問題總結

  解決這個問題對我們有什么好處呢,首先我們可以在這個過程中更加熟悉spring的bean的創建過程,以及mybatis的代理的生成過程,以及spring-mybatis集成的相關了解,了解這個我們可以更好的寫一些類去設計和mybatis更好地連接。

 

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM