写点什么

Spring 整合 MyBatis 详细分析

用户头像
Java收录阁
关注
发布于: 2020 年 05 月 10 日

mybatis-spring官网

这里我们以传统的 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>
复制代码


  1. 在 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>
复制代码


  1. 然后在 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.CONFIGLOCATIONDELIMITERS));
}
复制代码


@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.AUTOWIREBYTYPE);
}
}
}
复制代码


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()方法,后面的流程就跟编程式的工程里面一模一样了。


发布于: 2020 年 05 月 10 日阅读数: 185
用户头像

Java收录阁

关注

士不可以不弘毅,任重而道远 2020.04.30 加入

喜欢收集整理Java相关技术文档的程序员,欢迎关注同名微信公众号 Java收录 阁获取更多文章

评论

发布
暂无评论
Spring整合MyBatis详细分析