前言
在前面几篇文章中我们主要分析了Mybatis的单独使用,在实际在常规项目开发中,大部分都会使用mybatis与Spring结合起来使用,毕竟现在不用Spring开发的项目实在太少了。本篇文章便来介绍下Mybatis如何与Spring结合起来使用,并介绍下其源码是如何实现的。
Mybatis-Spring使用
添加maven依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version> </dependency>Mybatis和Spring整合方式即mybatis-spring
在src/main/resources下添加mybatis-config.xml文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <typeAlias alias="User" type="com.yibo.bean.User" /> </typeAliases> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> </plugin> </plugins> </configuration>在src/main/resources/mapper路径下添加User.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.yibo.mapper.UserMapper"> <select id="getUser" parameterType="int" resultType="com.yibo.bean.User"> SELECT * FROM USER WHERE id = #{id} </select> </mapper>在src/main/resources/路径下添加beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.chenhao.mapper" /> </bean> </beans>注解的方式
-
以上分析都是在spring的XML配置文件applicationContext.xml进行配置的,mybatis-spring也提供了基于注解的方式来配置sqlSessionFactory和Mapper接口。
-
sqlSessionFactory主要是在@Configuration注解的配置类中使用@Bean注解的名为sqlSessionFactory的方法来配置。
-
Mapper接口主要是通过在@Configuration注解的配置类中结合@MapperScan注解来指定需要扫描获取mapper接口的包。
对照Spring-Mybatis的方式,也就是对照beans.xml文件来看
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean>就对应着SqlSessionFactory的生成,类似于原生Mybatis使用时的以下代码
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build( Resources.getResourceAsStream("mybatis-config.xml"));而UserMapper代理对象的获取,是通过扫描的形式获取,也就是MapperScannerConfigurer这个类
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.yibo.mapper" /> </bean>对应着Mapper接口的获取,类似于原生Mybatis使用时的以下代码:
qlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class);Mybatis和SpringBoot整合方式
即引入mybatis-spring-boot-starter和mapper-spring-boot-starter
- application.properties中的配置
- 主配置类中加入@MapperScan
这样我们就可以在Service中直接从Spring的BeanFactory中获取了,如下
public class UserServiceImpl implements UserService{ @Autowired private UserMapper userMapper; }所以我们现在就主要分析下在Spring中是如何生成SqlSessionFactory和Mapper接口的
SqlSessionFactoryBean的设计与实现
大体思路:
-
mybatis-spring为了实现spring对mybatis的整合,即将mybatis的相关组件作为spring的IOC容器的bean来管理,使用了spring的FactoryBean接口来对mybatis的相关组件进行包装。spring的IOC容器在启动加载时,如果发现某个bean实现了FactoryBean接口,则会调用该bean的getObject方法,获取实际的bean对象注册到IOC容器,其中FactoryBean接口提供了getObject方法的声明,从而统一spring的IOC容器的行为。
-
SqlSessionFactory作为mybatis的启动组件,在mybatis-spring中提供了SqlSessionFactoryBean来进行包装,所以在spring项目中整合mybatis,首先需要在spring的配置,如XML配置文件applicationContext.xml中,配置SqlSessionFactoryBean来引入SqlSessionFactory,即在spring项目启动时能加载并创建SqlSessionFactory对象,然后注册到spring的IOC容器中,从而可以直接在应用代码中注入使用或者作为属性,注入到mybatis的其他组件对应的bean对象。在applicationContext.xml的配置如下:
接口设计与实现
SqlSessionFactory的接口设计如下:实现了spring提供的FactoryBean,InitializingBean和ApplicationListener这三个接口,在内部封装了mybatis的相关组件作为内部属性,如mybatisConfig.xml配置资源文件引用,mapper.xml配置资源文件引用,以及SqlSessionFactoryBuilder构造器和SqlSessionFactory引用。
// 解析mybatisConfig.xml文件和mapper.xml,设置数据源和所使用的事务管理机制,将这些封装到Configuration对象 // 使用Configuration对象作为构造参数,创建SqlSessionFactory对象,其中SqlSessionFactory为单例bean,最后将SqlSessionFactory单例对象注册到spring容器。 public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class); // mybatis配置mybatisConfig.xml的资源文件 private Resource configLocation; //解析完mybatisConfig.xml后生成Configuration对象 private Configuration configuration; // mapper.xml的资源文件 private Resource[] mapperLocations; // 数据源 private DataSource dataSource; // 事务管理,mybatis接入spring的一个重要原因也是可以直接使用spring提供的事务管理 private TransactionFactory transactionFactory; private Properties configurationProperties; // mybatis的SqlSessionFactoryBuidler和SqlSessionFactory private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; // 实现FactoryBean的getObject方法 public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { this.afterPropertiesSet(); } return this.sqlSessionFactory; } // 实现InitializingBean的 public void afterPropertiesSet() throws Exception { Assert.notNull(this.dataSource, "Property 'dataSource' is required"); Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = this.buildSqlSessionFactory(); } // 单例 public boolean isSingleton() { return true; } }我们重点关注FactoryBean,InitializingBean这两个接口,spring的IOC容器在加载创建SqlSessionFactoryBean的bean对象实例时,会调用InitializingBean的afterPropertiesSet方法进行对该bean对象进行相关初始化处理。
InitializingBean的afterPropertiesSet方法
大家最好看一下我前面关于Spring源码的文章,有Bean的生命周期详细源码分析,我们现在简单回顾一下,在getBean()时initializeBean方法中调用InitializingBean的afterPropertiesSet,而在前一步操作populateBean中,以及将该bean对象实例的属性设值好了,InitializingBean的afterPropertiesSet进行一些后置处理。此时我们要注意,populateBean方法已经将SqlSessionFactoryBean对象的属性进行赋值了,也就是xml中property配置的dataSource,mapperLocations,configLocation这三个属性已经在SqlSessionFactoryBean对象的属性进行赋值了,后面调用afterPropertiesSet时直接可以使用这三个配置的值了。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { // bean对象实例创建的核心实现方法 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. // 1.新建Bean包装类 BeanWrapper instanceWrapper = null; //如果RootBeanDefinition是单例的,则移除未完成的FactoryBean实例的缓存 if (mbd.isSingleton()) { // 2.如果是FactoryBean,则需要先移除未完成的FactoryBean实例的缓存 instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 3.根据beanName、mbd、args,使用对应的策略创建Bean实例,并返回包装类BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); } // 4.拿到创建好的Bean实例 final Object bean = instanceWrapper.getWrappedInstance(); // 5.拿到Bean实例的类型 Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { // 6.应用后置处理器MergedBeanDefinitionPostProcessor,允许修改MergedBeanDefinition, // Autowired注解、Value注解正是通过此方法实现注入类型的预解析 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // 7.判断是否需要提早曝光实例:单例 && 允许循环依赖 && 当前bean正在创建中 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 8.提前曝光beanName的ObjectFactory,用于解决循环引用 // 8.1 应用后置处理器SmartInstantiationAwareBeanPostProcessor,允许返回指定bean的早期引用,若没有则直接返回bean addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. // 初始化bean实例。 Object exposedObject = bean; try { // InstantiationAwareBeanPostProcessor执行: // (1). 调用InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation, // (2). 调用InstantiationAwareBeanPostProcessor的postProcessProperties和postProcessPropertyValues // 9.对bean进行属性填充;其中,可能存在依赖于其他bean的属性,则会递归初始化依赖的bean实例 populateBean(beanName, mbd, instanceWrapper); // Aware接口的方法调用 // BeanPostProcess执行:调用BeanPostProcessor的postProcessBeforeInitialization // 调用init-method:首先InitializingBean的afterPropertiesSet,然后应用配置的init-method // BeanPostProcess执行:调用BeanPostProcessor的postProcessAfterInitialization // 10.对bean进行初始化 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { // 11.如果允许提前曝光实例,则进行循环依赖检查 Object earlySingletonReference = getSingleton(beanName, false); // 11.1 earlySingletonReference只有在当前解析的bean存在循环依赖的情况下才会不为空 if (earlySingletonReference != null) { if (exposedObject == bean) { // 11.2 如果exposedObject没有在initializeBean方法中被增强,则不影响之前的循环引用 exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { // 11.3 如果exposedObject在initializeBean方法中被增强 && 不允许在循环引用的情况下使用注入原始bean实例 // && 当前bean有被其他bean依赖 // 11.4 拿到依赖当前bean的所有bean的beanName数组 String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { // 11.5 尝试移除这些bean的实例,因为这些bean依赖的bean已经被增强了,他们依赖的bean相当于脏数据 if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { // 11.6 移除失败的添加到 actualDependentBeans actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { // 11.7 如果存在移除失败的,则抛出异常,因为存在bean依赖了“脏数据” throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { // 12.注册用于销毁的bean,执行销毁操作的有三种:自定义destroy方法、DisposableBean接口、DestructionAwareBeanPostProcessor registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } // 13.完成创建并返回 return exposedObject; } }如上,在populateBean阶段,dataSource,mapperLocations,configLocation这三个属性已经在SqlSessionFactoryBean对象的属性进行赋值了,调用afterPropertiesSet时直接可以使用这三个配置的值了。那我们来接着看看afterPropertiesSet方法
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { @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"); // 创建sqlSessionFactory this.sqlSessionFactory = buildSqlSessionFactory(); } }SqlSessionFactoryBean的afterPropertiesSet方法实现如下:调用buildSqlSessionFactory方法创建用于注册到spring的IOC容器的sqlSessionFactory对象。我们接着来看看buildSqlSessionFactory
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { protected SqlSessionFactory buildSqlSessionFactory() throws IOException { // 配置类 Configuration configuration; // 解析mybatis-Config.xml文件, // 将相关配置信息保存到configuration XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } //资源文件不为空 } else if (this.configLocation != null) { //根据configLocation创建xmlConfigBuilder,XMLConfigBuilder构造器中会创建Configuration对象 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); //将XMLConfigBuilder构造器中创建的Configuration对象直接赋值给configuration属性 configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { //解析mybatis-Config.xml文件,并将相关配置信息保存到configuration xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { 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(); } } if (this.transactionFactory == null) { //事务默认采用SpringManagedTransaction,这一块非常重要,我将在后买你单独写一篇文章讲解Mybatis和Spring事务的关系 this.transactionFactory = new SpringManagedTransactionFactory(); } // 为sqlSessionFactory绑定事务管理器和数据源 // 这样sqlSessionFactory在创建sqlSession的时候可以通过该事务管理器获取jdbc连接,从而执行SQL configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // 解析mapper.xml if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { // 解析mapper.xml文件,并注册到configuration对象的mapperRegistry XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } // 将Configuration对象实例作为参数, // 调用sqlSessionFactoryBuilder创建sqlSessionFactory对象实例 return this.sqlSessionFactoryBuilder.build(configuration); } }buildSqlSessionFactory的核心逻辑:解析mybatis配置文件mybatisConfig.xml和mapper配置文件mapper.xml并封装到Configuration对象中,最后调用mybatis的sqlSessionFactoryBuilder来创建SqlSessionFactory对象。这一点相当于前面介绍的原生的mybatis的初始化过程。另外,当配置中未指定事务时,mybatis-spring默认采用SpringManagedTransaction,这一点非常重要,请大家先在心里做好准备。此时SqlSessionFactory已经创建好了,并且赋值到了SqlSessionFactoryBean的sqlSessionFactory属性中。
FactoryBean的getObject方法定义
FactoryBean:创建某个类的对象实例的工厂。
spring的IOC容器在启动,创建好bean对象实例后,会检查这个bean对象是否实现了FactoryBean接口,如果是,则调用该bean对象的getObject方法,在getObject方法中实现创建并返回实际需要的bean对象实例,然后将该实际需要的bean对象实例注册到spring容器;如果不是则直接将该bean对象实例注册到spring容器。
SqlSessionFactoryBean的getObject方法实现如下:由于spring在创建SqlSessionFactoryBean自身的bean对象时,已经调用了InitializingBean的afterPropertiesSet方法创建了sqlSessionFactory对象,故可以直接返回sqlSessionFactory对象给spring的IOC容器,从而完成sqlSessionFactory的bean对象的注册,之后可以直接在应用代码注入或者spring在创建其他bean对象时,依赖注入sqlSessionFactory对象。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } // 直接返回sqlSessionFactory对象 // 单例对象,由所有mapper共享 return this.sqlSessionFactory; } }总结
由以上分析可知,spring在加载创建SqlSessionFactoryBean的bean对象实例时,调用SqlSessionFactoryBean的afterPropertiesSet方法完成了sqlSessionFactory对象实例的创建;在将SqlSessionFactoryBean对象实例注册到spring的IOC容器时,发现SqlSessionFactoryBean实现了FactoryBean接口,故不是SqlSessionFactoryBean对象实例自身需要注册到spring的IOC容器,而是SqlSessionFactoryBean的getObject方法的返回值对应的对象需要注册到spring的IOC容器,而这个返回值就是SqlSessionFactory对象,故完成了将sqlSessionFactory对象实例注册到spring的IOC容器。
参考: https://www.cnblogs.com/java-chen-hao/p/11833780.html