• 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更好地连接。

        

  • 相关阅读:
    Navicat Premium 12.1.12.0破解版激活
    vConsole调试器
    使用DbFunctions来解决asp.net mvc ef按照日期分组数据
    谷歌浏览器如何安装CRX插件?crx离线插件安装方法
    ASP.NET MVC——CodeFirst开发模式
    Sql server 事务的两种用法
    SQL Server 存储过程
    JqueryMobile新手问题大全
    .net core 轻量级容器 ServiceProvider 源码分析
    Centos7 使用Docker 部署mssql 2017
  • 原文地址:https://www.cnblogs.com/zcmzex/p/8877697.html
Copyright © 2020-2023  润新知