spring如何管理mybatis ----- 动态代理接口

Posted 张小明儿java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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更好地连接。

 

 

    

以上是关于spring如何管理mybatis ----- 动态代理接口的主要内容,如果未能解决你的问题,请参考以下文章

Mybaits 源码解析 ----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?

spring如何管理mybatis ----- 动态代理接口

从源码角度剖析 Spring 如何管理 mybatis 事务的

spring如何管理mybatis ----- SqlSession的线程安全性

spring整合mybatis

eclipse如何搭建springmvc +mybatis