暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring整合MyBatis

Java收录阁 2020-05-04
637

mybatis-spring官网(http://www.mybatis.org/spring/zh/index.html)

这里我们以传统的spring为例,因为配置更为直观,在spring中使用注解的效果是一样的。


我们在其它几篇文章中已经介绍了MyBatis的工作流程、核心模块和底层原理。了解了MyBatis的原生API里面有三个核心对象:

SqlSessionFactory、SqlSession和MapperProxy

大部分时候我们不会在项目中单独使用MyBatis的工程,而是集成到Spring中使用,但是却没有看到这三个对象在代码里面出现。我们都是直接注入一个Mapper接口,然后调用Mapper接口的方法。所以有下面几个问题,我们要弄清楚:

1. SqlSessionFactory是什么时候创建的

2. SqlSession去哪里了?为什么不用它来获取Mapper?

3. 为什么@Autowired注入一个接口,在使用的时候却变成了一个代理对象?在IOC的容器里面我们注入的是什么?注入的时候发生了什么事情?


下面,先看一下把MyBatis集成到Spring中要做的几件事:

1. 除了MyBatis的依赖之外,我们还需要在pom中添加MyBatis和Spring整合的依赖

    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.0</version>
    </dependency>

    2. 在Spring的applicationContext.xml中配置SqlSessionFactoryBean,它是用来帮助我们创建会话的,其中还要指定全局配置文件和mapper映射器文件的路径

      <!-- 在Spring启动时创建 sqlSessionFactory -->
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="configLocation" value="classpath:mybatis-config.xml"></property>
      <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
      <property name="dataSource" ref="dataSource"/>
      </bean>

      3. 然后在applicationContext.xml中配置需要扫描Mapper接口的路径:

      在MyBatis中有几种方式,第一种是配置一个MapperScannerConfigurer,第二种是使用scan标签

        <!--配置扫描器,将mybatis的接口实现加入到  IOC容器中  -->
        <!--
        <mybatis-spring:scan #base-package="com.yrk.dao"/>
        -->
        <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.yrk.dao"/>
        </bean>

        还有一种是使用注解@MapperScan,比如我们在springboot的启动类上加上一个注解

          @SpringBootApplication
          @MapperScan("com.yrk.mybatis")
          public class MyBatisAnnotationApplication {
          /**
          * @param args
          */
          public static void main(String[] args) {
          SpringApplication.run(MyBatisAnnotationApplication.class, args);
          }
          }

          创建会话工厂

          Spring对MyBatis的对象进行管理,并不是替换MyBatis的核心对象,也就意味着MyBatis中SqlSessionFactory、SqlSession和MapperProxy这些类都会用到,而mybatis-spring.jar里面的类只是做了一些封装或者桥梁的工作。所以,第一步我们先看一下在spring里面是怎么创建工厂类的:

          我们在Spring的配置文件中配置了一个SqlSessionFactoryBean,我们来看一下这个类

          它实现了InitializingBean接口,所以要实现afterPropertiesSet()方法,这个方法会在Bean的属性值设置完成时被调用。另外,它实现了FactoryBean接口,所以在初始化的时候,实际上是调用getObject()方法,它里面调用的也是afterPropertiesSet()方法

            @Override
            public SqlSessionFactory getObject() throws Exception {
            if (this.sqlSessionFactory == null) {
            afterPropertiesSet();
                }
            return this.sqlSessionFactory;
            }

            在afterPropertiesSet方法中,调用了buildSqlSessionFactory()方法。

              @Override
              public void afterPropertiesSet() throws Exception {
              notNull(dataSource, "Property 'dataSource' is required");
              notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
              state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");


              this.sqlSessionFactory = buildSqlSessionFactory();
              }

              在buildSqlSessionFactory()方法中,第一步我们定义了一个Configuration,叫做targetConfiguration;

              426 行,判断Configuration 对象是否已经存在,也就是是否已经解析过。如果已经有对象,就覆盖一下属性。

              433 行,如果Configuration 不存在,但是配置了configLocation 属性,就根据mybatis-config.xml 的文件路径,构建一个xmlConfigBuilder 对象。

              436 行,否则,Configuration 对象不存在,configLocation 路径也没有,只能使用默认属性去构建去给configurationProperties 赋值。

              后面就是基于当前factory 对象里面已有的属性,对targetConfiguration 对象里面属性的赋值。

              在第498 行,如果xmlConfigBuilder 不为空,也就是上面的第二种情况,调用了xmlConfigBuilder.parse()去解析配置文件,最终会返回解析好的Configuration 对象,这里的parse()方法是MyBatis里面的方法,和我们单独使用MyBatis时候的解析全局配置文件的过程是一样的。

              在第507 行, 如果没有明确指定事务工厂, 默认使用SpringManagedTransactionFactory 。它创建SpringManagedTransaction 也有getConnection()和close()方法。

              在520 行,调用xmlMapperBuilder.parse(),这个步骤我们之前了解过了,它的作用是把接口和对应的MapperProxyFactory 注册到MapperRegistry 中。

              最后调用sqlSessionFactoryBuilder.build() 返回了一个DefaultSqlSessionFactory。OK,在这里我们完成了编程式的案例里面的第一步,根据配置文件获得一个工厂类,它是单例的,会在后面用来创建SqlSession。

                protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
                final Configuration targetConfiguration;
                XMLConfigBuilder xmlConfigBuilder = null;
                if (this.configuration != null) {
                targetConfiguration = this.configuration;
                if (targetConfiguration.getVariables() == null) {
                targetConfiguration.setVariables(this.configurationProperties);
                } else if (this.configurationProperties != null) {
                targetConfiguration.getVariables().putAll(this.configurationProperties);
                }
                } else if (this.configLocation != null) {
                xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
                targetConfiguration = xmlConfigBuilder.getConfiguration();
                } else {
                LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
                targetConfiguration = new Configuration();
                Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
                }
                Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
                Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
                Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);


                if (hasLength(this.typeAliasesPackage)) {
                String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
                Stream.of(typeAliasPackageArray).forEach(packageToScan -> {
                targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
                LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
                });
                }
                if (!isEmpty(this.typeAliases)) {
                Stream.of(this.typeAliases).forEach(typeAlias -> {
                targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
                LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
                });
                }
                if (!isEmpty(this.plugins)) {
                Stream.of(this.plugins).forEach(plugin -> {
                targetConfiguration.addInterceptor(plugin);
                LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
                });
                }
                if (hasLength(this.typeHandlersPackage)) {
                String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
                Stream.of(typeHandlersPackageArray).forEach(packageToScan -> {
                targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
                LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
                });
                }
                if (!isEmpty(this.typeHandlers)) {
                Stream.of(this.typeHandlers).forEach(typeHandler -> {
                targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
                LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
                });
                }
                if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
                try {
                targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
                } catch (SQLException e) {
                throw new NestedIOException("Failed getting a databaseId", e);
                }
                }
                Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
                if (xmlConfigBuilder != null) {
                try {
                xmlConfigBuilder.parse();
                LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
                } catch (Exception ex) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
                } finally {
                ErrorContext.instance().reset();
                }
                }
                targetConfiguration.setEnvironment(new Environment(this.environment,
                this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
                this.dataSource));
                if (!isEmpty(this.mapperLocations)) {
                for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                continue;
                }
                try {
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                xmlMapperBuilder.parse();
                } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                ErrorContext.instance().reset();
                }
                LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
                }
                } else {
                LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
                }
                return this.sqlSessionFactoryBuilder.build(targetConfiguration);
                }


                创建SqlSession

                Q1: 可以直接使用DefaultSqlSession吗?

                通过上面一步,我们现在已经有一个DefaultSqlSessionFactory了,按照编程式的开发过程,接下来我们要创建一个SqlSession的实现类,但是在Spring中,我们不是直接使用DefaultSqlSession的,而是对它进行了一个封装,这个SqlSession的实现类就是SqlSessionTemplate。这个跟Spring封装其他的组件也是一样的,比如JdbcTemplate,RedisTemplate等等,也是Spring跟MyBatis整合最关键的一个类。


                为什么不用DefaultSqlSession?因为它不是线程安全的,而SqlSessionTemplate是线程安全的。

                  /**
                  * The default implementation for {@link SqlSession}.
                  * Note that this class is not Thread-Safe.
                  *
                  * @author Clinton Begin
                  */
                  public class DefaultSqlSession implements SqlSession {


                    /**
                    * Thread safe, Spring managed, {@code SqlSession} that works with Spring
                    * transaction management to ensure that that the actual SqlSession used is the
                    * one associated with the current Spring transaction. In addition, it manages
                    * the session life-cycle, including closing, committing or rolling back the
                    * session as necessary based on the Spring transaction configuration.
                    * <p>
                    * The template needs a SqlSessionFactory to create SqlSessions, passed as a
                    * constructor argument. It also can be constructed indicating the executor type
                    * to be used, if not, the default executor type, defined in the session factory
                    * will be used.
                    * <p>
                    * This template converts MyBatis PersistenceExceptions into unchecked
                    * DataAccessExceptions, using, by default, a {@code MyBatisExceptionTranslator}.
                    * <p>
                    * Because SqlSessionTemplate is thread safe, a single instance can be shared
                    * by all DAOs; there should also be a small memory savings by doing this. This
                    * pattern can be used in Spring configuration files as follows:
                    *
                    * <pre class="code">
                    * {@code
                    * <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
                    * <constructor-arg ref="sqlSessionFactory" >
                    * </bean>
                    * }
                    * </pre>
                    *
                    * @author Putthiphong Boonphong
                    * @author Hunter Presnall
                    * @author Eduardo Macarron
                    *
                    * @see SqlSessionFactory
                    * @see MyBatisExceptionTranslator
                    */
                    public class SqlSessionTemplate implements SqlSession, DisposableBean {

                    在编程式的开发中,SqlSession我们会在每次请求的时候创建一个,但是Spring里面只有一个SqlSessionTemplate(默认是单例的),多个线程同时调用的时候如何保证线程安全?

                    SqlSessionTemplate里面有DefaultSqlSession中的所有方法,selectOne()、selectList()、insert()、update()、delete(),不过它都是通过一个代理对象实现的。这个代理对象在SqlSessionTemplate的构造方法中通过一个代理类创建:

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

                      SqlSessionTemplate中的所有方法都会先走到内部代理类SqlSessionInterceptor的invoke方法:

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

                        首先会使用工厂类、执行器类型、异常解析器创建一个sqlSession。

                          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;
                          }
                          LOGGER.debug(() -> "Creating a new SqlSession");
                          session = sessionFactory.openSession(executorType);
                          registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
                          return session;
                          }

                          在getSqlSession方法中,会先去从SqlSessionHolder中获取SqlSession,如果获取到就直接返回,如果没有就新创建一个,并把这个新创建的SqlSession注册到SqlSessionHolder中。SqlSessionHolder是通过TransactionSynchronizationManager.getResource()方法获取到的,实际上是从ThreadLocal中获取,所以SqlSessionTemplate是线程安全的。

                            private static final ThreadLocal<Map<Object, Object>> resources =
                            new NamedThreadLocal<>("Transactional resources");

                            @Nullable
                            public static Object getResource(Object key) {
                            Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
                            Object value = doGetResource(actualKey);
                            if (value != null && logger.isTraceEnabled()) {
                            logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
                            Thread.currentThread().getName() + "]");
                            }
                            return value;
                            }


                            /**
                            * Actually check the value of the resource that is bound for the given key.
                            */
                            @Nullable
                            private static Object doGetResource(Object actualKey) {
                            Map<Object, Object> map = resources.get();
                            if (map == null) {
                            return null;
                            }
                            Object value = map.get(actualKey);
                            // Transparently remove ResourceHolder that was marked as void...
                            if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
                            map.remove(actualKey);
                            // Remove entire ThreadLocal if empty...
                            if (map.isEmpty()) {
                            resources.remove();
                            }
                            value = null;
                            }
                            return value;
                            }

                            Q2: 怎么拿到一个SqlSessionTemplate?

                            我们知道在Spring里面会用SqlSessionTemplate替换DefaultSqlSession,那么接下来看一下怎么在DAO层拿到一个SqlSessionTemplate。

                            用过Hibernate的同学应该记得,如果不用注入式的方式,我们在DAO层注入一个HibernateTemplate的一种方式是让我们的DAO层的实现类去集成HibernateDaoSupport。MyBatis也是一样,它提供一个SqlSessionDaoSupport, 里面持有SqlSessionTemplate对象,并且提供了一个getSqlSession()方法,让我们获得一个SqlSessionTemplate:

                              public abstract class SqlSessionDaoSupport extends DaoSupport {


                              private SqlSessionTemplate sqlSessionTemplate;

                              /**
                              * 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.sqlSessionTemplate;
                              }
                              //省略部分代码

                              也就是说我们让DAO 层的实现类继承SqlSessionDaoSupport ,就可以获得SqlSessionTemplate,然后在里面封装SqlSessionTemplate 的方法。

                                public  class BaseDao extends SqlSessionDaoSupport {
                                //使用sqlSessionFactory
                                @Autowired
                                private SqlSessionFactory sqlSessionFactory;


                                @Autowired
                                public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
                                super.setSqlSessionFactory(sqlSessionFactory);
                                }


                                /**
                                * @param statement
                                * @return
                                */
                                public Object selectOne(String statement) {
                                return getSqlSession().selectOne(statement);
                                }
                                }

                                Q3: 有没有更好的方法可以拿到SqlSessionTemplate? Spring中我们的DAO层没有集成SqlSessionDaoSupport,那Spring中是怎么拿到SqlSessionTemplate的?

                                在真实项目中,我们只是在DAO层使用@Autowired注入Mapper对象,然后直接调用Mapper对象的方法去操作数据库,那Mapper对象的实例一定是在Spring启动的时候被Spring扫描并且注册了。那这个Mapper是什么时候扫描的?注册的时候又是注册成什么对象?


                                回顾一下,我们在applicationContext.xml中配置了一个MapperScannerConfigurer,MapperScannerConfigurer 实现了BeanDefinitionRegistryPostProcessor 接口,BeanDefinitionRegistryPostProcessor 是BeanFactoryPostProcessor 的子类,可以通过编码的方式修改、新增或者删除某些Bean 的定义。

                                我们只需要重写postProcessBeanDefinitionRegistry()方法,在这里面操作Bean就可以了。在这个方法中,scanner.scan() 方法是ClassPathBeanDefinitionScanner 中的, 而它的子类ClassPathMapperScanner 覆盖了doScan() 方法, 在doScan() 中调用了processBeanDefinitions,在processBeanDefinitions方法中,在注册beanDefinitions 的时候,BeanClass被改为MapperFactoryBean。


                                为什么要把beanClass改成MapperFactoryBean呢?因为MapperFactoryBean继承了SqlSessionDaoSupport,可以获取到SqlSessionTemplate

                                  @Override
                                  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
                                  if (this.processPropertyPlaceHolders) {
                                  processPropertyPlaceHolders();
                                  }


                                  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
                                  scanner.setAddToConfig(this.addToConfig);
                                  scanner.setAnnotationClass(this.annotationClass);
                                  scanner.setMarkerInterface(this.markerInterface);
                                  scanner.setSqlSessionFactory(this.sqlSessionFactory);
                                  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
                                  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
                                  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
                                  scanner.setResourceLoader(this.applicationContext);
                                  scanner.setBeanNameGenerator(this.nameGenerator);
                                  scanner.registerFilters();
                                  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
                                  }
                                    @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 {
                                    processBeanDefinitions(beanDefinitions);
                                    }


                                    return beanDefinitions;
                                    }


                                    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
                                    GenericBeanDefinition definition;
                                    for (BeanDefinitionHolder holder : beanDefinitions) {
                                    definition = (GenericBeanDefinition) holder.getBeanDefinition();
                                    String beanClassName = definition.getBeanClassName();
                                    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
                                    + "' and '" + beanClassName + "' mapperInterface");


                                    // the mapper interface is the original class of the bean
                                    // but, the actual class of the bean is MapperFactoryBean
                                    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
                                    definition.setBeanClass(this.mapperFactoryBean.getClass());


                                    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) {
                                    LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                                    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
                                    }
                                    }
                                    }
                                      public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {


                                      private Class<T> mapperInterface;


                                      private boolean addToConfig = true;


                                      public MapperFactoryBean() {
                                      //intentionally empty
                                      }
                                      //省略部分代码
                                      }

                                      我们使用Mapper 的时候,只需要在加了Service 注解的类里面使用@Autowired注入Mapper 接口就好了。

                                        @Service
                                        public class EmployeeService {
                                        @Autowired
                                        EmployeeMapper employeeMapper;
                                        public List<Employee> getAll() {
                                        return employeeMapper.selectByMap(null);
                                        }
                                        public void saveEmpsInfo(Employee employee) {
                                        employeeMapper.insertSelective(employee);
                                        }
                                        }

                                        Spring在启动的时候需要去实例化EmployeeService,EmployeeService又依赖了EmployeeMapper接口,Spring会根据Mapper的名字从BeanFactory中获取它的BeanDefinition,再从BeanDefinition中获取BeanClass,上面已经提到,Mapper的BeanClass已经被改成MapperFactoryBean,所以EmployeeMapper的beanClass是MapperFactoryBean。


                                        接下来是创建MapperFactoryBean,因为MapperFactoryBean实现了FactoryBean接口,创建的时候会调用getObject()方法:

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

                                          因为MapperFactoryBean继承了SqlSessionDaoSupport,所以getSqlSession()返回的就是SqlSessionTemplate。

                                            @Override
                                            public <T> T getMapper(Class<T> type) {
                                            return getConfiguration().getMapper(type, this);
                                            }

                                            SqlSessionTemplate的getMapper()方法又是调用Configuration的getMapper()方法,跟编程式使用里面的getMapper 一样, 通过工厂类MapperProxyFactory 获得一个MapperProxy 代理对象。


                                            也就是说,我们注入到Service 层的接口,实际上还是一个MapperProxy 代理对象。所以最后调用Mapper 接口的方法,也是执行MapperProxy 的invoke()方法,后面的流程就跟编程式的工程里面一模一样了。




                                            文章转载自Java收录阁,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                            评论