該系列文檔是本人在學習 Mybatis 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋(Mybatis源碼分析 GitHub 地址、Mybatis-Spring 源碼分析 GitHub 地址、Spring-Boot-Starter 源碼分析 GitHub 地址)進行閱讀html
MyBatis 版本:3.5.2java
MyBatis-Spring 版本:2.0.3git
MyBatis-Spring-Boot-Starter 版本:2.1.4github
在前面的一系列文檔中對整個 MyBatis 框架進行了分析,相信你對 MyBatis 有了一個更加深刻的瞭解。在使用它的過程當中,須要本身建立 SqlSessionFactory 和 SqlSession,而後獲取到 Mapper 接口的動態代理對象,執行數據庫相關操做,對這些對象的管理並非那麼簡單。咱們一般會結合 Spring 來使用 MyBatis,將這些對象做爲 Spring Bean 注入到 Spring 容器,也容許參與到 Spring 的事務管理之中spring
Spring 官方並無提供對 MyBatis3 的集成方案,因而在 MyBatis 社區將對 Spring 的集成做爲一個 MyBatis 子項目 MyBatis-Spring,幫助你將 MyBatis 代碼無縫地整合到 Spring 中,那麼咱們一塊兒來看看這個子項目是如何集成到 Spring 中的sql
在開始讀這篇文檔以前,須要對 Spring 有必定的瞭解,能夠結合個人源碼註釋(Mybatis-Spring 源碼分析 GitHub 地址)進行閱讀,MyBatis-Spring官方文檔數據庫
主要涉及到的幾個類:apache
org.mybatis.spring.SqlSessionFactoryBean
:實現 FactoryBean、InitializingBean、ApplicationListener 接口,負責構建一個 SqlSessionFactory 對象數組
org.mybatis.spring.mapper.MapperFactoryBean
:實現 FactoryBean 接口,繼承 SqlSessionDaoSupport 抽象類,Mapper 接口對應 Spring Bean 對象,用於返回對應的動態代理對象緩存
org.mybatis.spring.support.SqlSessionDaoSupport
抽象類,繼承了 DaoSupport 抽象類,用於構建一個 SqlSessionTemplate 對象
org.mybatis.spring.mapper.MapperScannerConfigurer
:實現了BeanDefinitionRegistryPostProcessor、InitializingBean接口,ApplicationContextAware、BeanNameAware接口,用於掃描Mapper接口,藉助ClassPathMapperScanner
掃描器對Mapper接口的BeanDefinition對象(Spring Bean 的前身)進行修改
org.mybatis.spring.mapper.ClassPathMapperScanner
:繼承了ClassPathBeanDefinitionScanner抽象類,負責執行掃描,修改掃描到的 Mapper 接口的 BeanDefinition 對象(Spring Bean的前身),將其 Bean Class 修改成 MapperFactoryBean,從而在 Spring 初始化該 Bean 的時候,會初始化成 MapperFactoryBean
類型,實現建立 Mapper 動態代理對象
org.mybatis.spring.annotation.MapperScannerRegistrar
:實現 ImportBeanDefinitionRegistrar、ResourceLoaderAware 接口
做爲@MapperScann
註解的註冊器,根據註解信息註冊一個 MapperScannerConfigurer
對象,用於掃描 Mapper 接口
org.mybatis.spring.config.MapperScannerBeanDefinitionParser
:實現 BeanDefinitionParser 接口,<mybatis:scan />
的解析器,和MapperScannerRegistrar
的實現邏輯同樣
org.mybatis.spring.SqlSessionTemplate
:實現 SqlSession 和 DisposableBean 接口,SqlSession 操做模板實現類,承擔 SqlSessionFactory 和 SqlSession 的職責
org.mybatis.spring.SqlSessionUtils
:SqlSession 工具類,負責處理 MyBatis SqlSession 的生命週期,藉助 Spring 的 TransactionSynchronizationManager 事務管理器管理 SqlSession 對像
大體邏輯以下:
MapperScannerConfigurer
的 Spring Bean,它會結合 ClassPathMapperScanner
掃描器,對指定包路徑下的 Mapper 接口對應 BeanDefinition 對象(Spring Bean 的前身)進行修改,將其 Bean Class 修改成 MapperFactoryBean
類型,從而在 Spring 初始化該 Bean 的時候,會初始化成 MapperFactoryBean
對象,實現建立 Mapper 動態代理對象MapperFactoryBean
對象中getObject()
中,根據 SqlSessionTemplate
對象爲該 Mapper 接口建立一個動態代理對象,也就是說在咱們注入該 Mapper 接口時,實際注入的是 Mapper 接口對應的動態代理對象SqlSessionTemplate
對象中,承擔 SqlSessionFactory 和 SqlSession 的職責,結合 Spring 的事務體系進行處理<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${url}" /> <property name="driverClassName" value="${driver}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </bean> <!-- spring和MyBatis完美整合,不須要mybatis的配置映射文件 --> <bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- bean的名稱爲sqlSessionFactory會出現錯誤 --> <property name="dataSource" ref="dataSource" /> <!-- 引入配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 自動掃描mapping.xml文件 --> <property name="mapperLocations" value="classpath:com/fullmoon/study/mapping/*.xml" /> </bean> <!-- DAO接口所在包名,Spring會自動查找其下的類 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.fullmoon.study.dao" /> <property name="sqlSessionFactoryBeanName" value="mySqlSessionFactory" /> </bean>
DruidDataSource
數據源,SqlSessionFactoryBean
和MapperScannerConfigurer
對象org.mybatis.spring.SqlSessionFactoryBean
:實現 FactoryBean、InitializingBean、ApplicationListener 接口,負責構建一個 SqlSessionFactory 對象
關於Spring的FactoryBean
機制,不熟悉的先去了解一下,大體就是Spring在注入該類型的Bean時,調用的是它的getObject()
方法
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver(); private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory(); /** * 指定的 mybatis-config.xml 路徑的資源 */ private Resource configLocation; private Configuration configuration; /** * 指定 XML 映射文件路徑的資源數組 */ private Resource[] mapperLocations; /** * 數據源 */ private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); /** * SqlSession 工廠,默認爲 DefaultSqlSessionFactory */ 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; @SuppressWarnings("rawtypes") private Class<? extends TypeHandler> defaultEnumTypeHandler; private Class<?>[] typeAliases; private String typeAliasesPackage; private Class<?> typeAliasesSuperType; private LanguageDriver[] scriptingLanguageDrivers; private Class<? extends LanguageDriver> defaultScriptingLanguageDriver; // issue #19. No default provider. private DatabaseIdProvider databaseIdProvider; private Class<? extends VFS> vfs; private Cache cache; private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory; }
能夠看到上面定義的各類屬性,這裏就不一一解釋了,根據名稱能夠知道屬性的做用
afterPropertiesSet()
方法,實現的 InitializingBean 接口,在 Spring 容器中,初始化該 Bean 時,會調用該方法,方法以下:
@Override public void afterPropertiesSet() throws Exception { // 校驗 dataSource 數據源不能爲空 notNull(dataSource, "Property 'dataSource' is required"); // 校驗 sqlSessionFactoryBuilder 構建器不能爲空,上面默認 new 一個對象 notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); // configuration 和 configLocation 有且只有一個不爲空 state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); // 初始化 SqlSessionFactory this.sqlSessionFactory = buildSqlSessionFactory(); }
buildSqlSessionFactory()
方法,初始化 SqlSessionFactory
buildSqlSessionFactory()
方法,根據配置信息構建一個SqlSessionFactory
實例,方法以下:
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final Configuration targetConfiguration; // 初始化 Configuration 全局配置對象 XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { // 若是已存在 Configuration 對象 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) { // 不然,若是配置了 mybatis-config.xml 配置文件 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"); // 不然,建立一個 Configuration 對象 targetConfiguration = new Configuration(); Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); } /* * 若是配置了 ObjectFactory(實例工廠)、ObjectWrapperFactory(ObjectWrapper工廠)、VFS(虛擬文件系統) * 則分別往 Configuration 全局配置對象設置 */ Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory); Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory); Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl); /* * 若是配置了須要設置別名的包路徑,則掃描該包路徑下的 Class 對象 * 往 Configuration 全局配置對象的 TypeAliasRegistry 別名註冊表進行註冊 */ if (hasLength(this.typeAliasesPackage)) { scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() // 過濾掉匿名類 .filter(clazz -> !clazz.isAnonymousClass()) // 過濾掉接口 .filter(clazz -> !clazz.isInterface()) // 過濾掉內部類 .filter(clazz -> !clazz.isMemberClass()) .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); } /* * 若是單獨配置了須要設置別名的 Class 對象 * 則將其往 Configuration 全局配置對象的 TypeAliasRegistry 別名註冊表註冊 */ if (!isEmpty(this.typeAliases)) { Stream.of(this.typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'"); }); } // 往 Configuration 全局配置對象添加 Interceptor 插件 if (!isEmpty(this.plugins)) { Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); }); } // 掃描包路徑,往 Configuration 全局配置對象添加 TypeHandler 類型處理器 if (hasLength(this.typeHandlersPackage)) { scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) .forEach(targetConfiguration.getTypeHandlerRegistry()::register); } // 往 Configuration 全局配置對象添加 TypeHandler 類型處理器 if (!isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); }); } // 設置默認的枚舉類型處理器 targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler); // 往 Configuration 全局配置對象添加 LanguageDriver 語言驅動 if (!isEmpty(this.scriptingLanguageDrivers)) { Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { targetConfiguration.getLanguageRegistry().register(languageDriver); LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'"); }); } // 設置默認的 LanguageDriver 語言驅動 Optional.ofNullable(this.defaultScriptingLanguageDriver) .ifPresent(targetConfiguration::setDefaultScriptingLanguage); // 設置當前數據源的數據庫 id 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); } } // 添加 Cache 緩存 Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache); if (xmlConfigBuilder != null) { try { // 若是配置了 mybatis-config.xml 配置文件,則初始化 MyBatis 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(); } } // 設置 Environment 環境信息 targetConfiguration.setEnvironment(new Environment(this.environment, this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource)); // 若是配置了 XML 映射文件的路徑 if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { /* * 遍歷全部的 XML 映射文件 */ for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); // 解析 XML 映射文件 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."); } // 經過構建器建立一個 DefaultSqlSessionFactory 對象 return this.sqlSessionFactoryBuilder.build(targetConfiguration); }
方法有點長,主要是經過配置信息建立一個 Configuration 對象,而後構建一個 DefaultSqlSessionFactory 對象
初始化 Configuration
全局配置對象
往 Configuration 對象中設置相關配置屬性
若是是1.2
步生成的 Configuration 對象,那麼調用 XMLConfigBuilder
的 parse()
方法進行解析,初始化 MyBatis,在《MyBatis 初始化(一)之加載mybatis-config.xml》中分析過
若是配置了 XML 映射文件的路徑mapperLocations
,則進行遍歷依次解析,經過建立XMLMapperBuilder
對象,調用其parse()
方法進行解析,在《MyBatis 初始化(二)之加載Mapper接口與映射文件》的XMLMapperBuilder小節中分析過
經過SqlSessionFactoryBuilder
構建器建立一個DefaultSqlSessionFactory
對象
在 MyBatis-Spring 項目中,除了經過 mybatis-config.xml 配置文件配置 Mapper 接口的方式之外,還提供了幾種配置方法,這裏的
SqlSessionFactoryBean.mapperLocations
也算一種這樣在 Spring 中,Mapper 接口和對應的 XML 映射文件名稱能夠不一致,文件中裏面配置
namepace
正確就能夠了
getObject()
方法,在 Spring 容器中,注入當前 Bean 時調用該方法,也就是返回 DefaultSqlSessionFactory 對象,方法以下:
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { // 若是爲空則初始化 sqlSessionFactory afterPropertiesSet(); } // 返回 DefaultSqlSessionFactory 對象 return this.sqlSessionFactory; }
onApplicationEvent(ApplicationEvent event)
方法,監聽 ContextRefreshedEvent 事件,若是還存在未初始化完成的 MapperStatement 們,則再進行解析,方法以下:
@Override public void onApplicationEvent(ApplicationEvent event) { // 若是配置了須要快速失敗,而且監聽到了 Spring 容器初始化完成事件 if (failFast && event instanceof ContextRefreshedEvent) { // fail-fast -> check all statements are completed // 將 MyBatis 中還未徹底解析的對象,在這裏再進行解析 this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } }
org.mybatis.spring.mapper.MapperFactoryBean
:實現 FactoryBean 接口,繼承 SqlSessionDaoSupport 抽象類,Mapper 接口對應 Spring Bean 對象,用於返回對應的動態代理對象
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { /** * Mapper 接口 */ private Class<T> mapperInterface; /** * 是否添加到 {@link Configuration} 中,默認爲 true */ private boolean addToConfig = true; public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } }
mapperInterface
:對應的Mapper接口
addToConfig
:是否添加到 Configuration 中,默認爲 true
getObject()
方法,在 Spring 容器中,注入當前 Bean 時調用該方法,也就是返回 Mapper 接口對應的動態代理對象,方法以下:
@Override public T getObject() throws Exception { // getSqlSession() 方法返回 SqlSessionTemplate 對象 return getSqlSession().getMapper(this.mapperInterface); }
getSqlSession()
方法在SqlSessionDaoSupport
中定義,返回的是SqlSessionTemplate
對象,後續會講到
能夠先暫時理解爲就是返回一個 DefaultSqlSession,獲取mapperInterface
Mapper接口對應的動態代理對象
這也就是爲何在Spring中注入Mapper接口Bean時,咱們能夠直接調用它的方法
checkDaoConfig()
方法,校驗該 Mapper 接口是否被初始化並添加到 Configuration 中
@Override protected void checkDaoConfig() { // 校驗 sqlSessionTemplate 非空 super.checkDaoConfig(); // 校驗 mapperInterface 非空 notNull(this.mapperInterface, "Property 'mapperInterface' is required"); /* * 若是該 Mapper 接口沒有被解析至 Configuration,則對其進行解析 */ Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { // 將該 Mapper 接口添加至 Configuration,會對該接口進行一系列的解析 configuration.addMapper(this.mapperInterface); } catch (Exception e) { logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); throw new IllegalArgumentException(e); } finally { ErrorContext.instance().reset(); } } }
sqlSessionTemplate
非空mapperInterface
非空由於繼承了DaoSupport
抽象類,實現了 InitializingBean 接口,在 afterPropertiesSet() 方法中會調用checkDaoConfig()
方法
org.mybatis.spring.support.SqlSessionDaoSupport
抽象類,繼承了 DaoSupport 抽象類,用於構建一個 SqlSessionTemplate 對象,代碼以下:
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSessionTemplate sqlSessionTemplate; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); } } @SuppressWarnings("WeakerAccess") protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } public final SqlSessionFactory getSqlSessionFactory() { return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null); } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } public SqlSession getSqlSession() { return this.sqlSessionTemplate; } public SqlSessionTemplate getSqlSessionTemplate() { return this.sqlSessionTemplate; } @Override protected void checkDaoConfig() { notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }
setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
方法,將 SqlSessionFactory 構建成咱們須要的 SqlSessionTemplate 對象,該對象在後續講到checkDaoConfig()
方法,校驗 SqlSessionTemplate 非空org.mybatis.spring.mapper.MapperScannerConfigurer
:實現了BeanDefinitionRegistryPostProcessor
、InitializingBean接口,ApplicationContextAware、BeanNameAware接口
用於掃描Mapper接口,藉助ClassPathMapperScanner
修改Mapper接口的BeanDefinition對象(Spring Bean 的前身),將Bean的Class對象修改成MapperFactoryBean
類型
那麼在Spring初始化該Bean的時候,會初始化成MapperFactoryBean類型
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { /** * Mapper 接口的包路徑 */ private String basePackage; /** * 是否要將接口添加到 Configuration 全局配置對象中 */ private boolean addToConfig = true; private String lazyInitialization; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionFactoryBeanName; private String sqlSessionTemplateBeanName; private Class<? extends Annotation> annotationClass; private Class<?> markerInterface; private Class<? extends MapperFactoryBean> mapperFactoryBeanClass; private ApplicationContext applicationContext; private String beanName; private boolean processPropertyPlaceHolders; private BeanNameGenerator nameGenerator; private String defaultScope; @Override public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); } }
在SqlSessionFactoryBean小節的示例中能夠看到,定義了basePackage
和sqlSessionFactoryBeanName
兩個屬性
basePackage
:Mapper 接口的包路徑addToConfig
:是否要將接口添加到 Configuration 全局配置對象中sqlSessionFactoryBeanName
:SqlSessionFactory的Bean Name在afterPropertiesSet()
方法中會校驗basePackage
非空
在 MyBatis-Spring 項目中,除了經過 mybatis-config.xml 配置文件配置 Mapper 接口的方式之外,還提供了幾種配置方法,這裏的配置
basePackage
屬性也算一種這樣在 Spring 中,Mapper 接口和對應的 XML 映射文件名稱能夠不一致,文件中裏面配置
namepace
正確就能夠了
postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
方法,在 BeanDefinitionRegistry 完成後進行一些處理
這裏會藉助ClassPathMapperScanner
掃描器,掃描指定包路徑下的 Mapper 接口,方法以下:
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { // 處理屬性中的佔位符 processPropertyPlaceHolders(); } // 建立一個 Bean 掃描器 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // 是否要將 Mapper 接口添加到 Configuration 全局配置對象中 scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); // 設置 SqlSessionFactory 的 BeanName scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } if (StringUtils.hasText(defaultScope)) { scanner.setDefaultScope(defaultScope); } // 添加幾個過濾器 scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
processPropertyPlaceHolders()
方法ClassPathMapperScanner
對象addToConfig
、sqlSessionFactoryBeanName
registerFilters()
方法,添加幾個過濾器,過濾指定路徑下的 Mapper 接口scan
方法,開始掃描 basePackage
路徑下的 Mapper 接口org.mybatis.spring.mapper.ClassPathMapperScanner
:繼承了ClassPathBeanDefinitionScanner抽象類
負責執行掃描,修改掃描到的 Mapper 接口的 BeanDefinition 對象(Spring Bean的前身),將其 Bean Class 修改成 MapperFactoryBean,從而在 Spring 初始化該 Bean 的時候,會初始化成 MapperFactoryBean
類型,實現建立 Mapper 動態代理對象
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class); /** * 是否要將 Mapper 接口添加到 Configuration 全局配置對象中 */ private boolean addToConfig = true; private boolean lazyInitialization; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionTemplateBeanName; /** * SqlSessionFactory Bean 的名稱 */ private String sqlSessionFactoryBeanName; private Class<? extends Annotation> annotationClass; private Class<?> markerInterface; /** * 將 Mapper 接口轉換成 MapperFactoryBean 對象 */ private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class; private String defaultScope; }
basePackage
:Mapper 接口的包路徑addToConfig
:是否要將接口添加到 Configuration 全局配置對象中sqlSessionFactoryBeanName
:SqlSessionFactory的Bean Name上面這幾個屬性在 MapperScannerConfigurer 建立該對象的時候會進行賦值
registerFilters()
方法,添加幾個過濾器,用於掃描 Mapper 接口的過程當中過濾出咱們須要的 Mapper 接口,方法以下:
public void registerFilters() { // 標記是否接受全部的 Mapper 接口 boolean acceptAllInterfaces = true; // if specified, use the given annotation and / or marker interface // 若是配置了註解,則掃描有該註解的 Mapper 接口 if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // override AssignableTypeFilter to ignore matches on the actual marker interface // 若是配置了某個接口,則也須要掃描該接口 if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } if (acceptAllInterfaces) { // default include filter that accepts all classes // 若是上面兩個都沒有配置,則接受全部的 Mapper 接口 addIncludeFilter((metadataReader, metadataReaderFactory) -> true); } // exclude package-info.java // 排除 package-info.java 文件 addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); }
2
、3
步,則添加一個過濾器,接收全部的接口doScan(String... basePackages)
方法,掃描指定包路徑,根據上面的過濾器,獲取路徑下對應的 BeanDefinition 集合,進行一些後置處理,方法以下:
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 掃描指定包路徑,根據上面的過濾器,獲取到路徑下 Class 對象的 BeanDefinition 對象 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 { // 後置處理這些 BeanDefinition processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
BeanDefinition
對象註冊到 BeanDefinitionRegistry 註冊表中,並返回processBeanDefinitions
方法,進行一些後置處理processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)
方法,對指定包路徑下符合條件的BeanDefinition
對象進行一些處理,修改其 Bean Class 爲 MapperFactoryBean
類型,方法以下:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; // <1> 獲取 BeanDefinition 註冊表,而後開始遍歷 BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); boolean scopedProxy = false; if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) { // 獲取被裝飾的 BeanDefinition 對象 definition = (AbstractBeanDefinition) Optional .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()) .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException( "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]")); scopedProxy = true; } // <2> 獲取對應的 Bean 的 Class 名稱,也就是 Mapper 接口的 Class 對象 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 // <3> 往構造方法的參數列表中添加一個參數,爲當前 Mapper 接口的 Class 對象 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 /* * <4> 修改該 Mapper 接口的 Class對象 爲 MapperFactoryBean.class * 這樣一來當你注入該 Mapper 接口的時候,實際注入的是 MapperFactoryBean 對象,構造方法的入參就是 Mapper 接口 */ definition.setBeanClass(this.mapperFactoryBeanClass); // <5> 添加 addToConfig 屬性 definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; // <6> 開始添加 sqlSessionFactory 或者 sqlSessionTemplate 屬性 /* * 1. 若是設置了 sqlSessionFactoryBeanName,則添加 sqlSessionFactory 屬性,實際上配置的是 SqlSessionFactoryBean 對象 * 2. 不然,若是配置了 sqlSessionFactory 對象,則添加 sqlSessionFactory 屬性 */ 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; } /* * 1. 若是配置了 sqlSessionTemplateBeanName,則添加 sqlSessionTemplate 屬性 * 2. 不然,若是配置了 sqlSessionTemplate 對象,則添加 sqlSessionTemplate 屬性 * SqlSessionFactory 和 SqlSessionTemplate 都配置了則會打印一個警告 */ if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { // 若是上面已經清楚的使用了 SqlSessionFactory,則打印一個警告 LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } // 添加 sqlSessionTemplate 屬性 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; } /* * 上面沒有找到對應的 SqlSessionFactory,則設置經過類型注入 */ if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); if (scopedProxy) { // 已經封裝過的則直接執行下一個 continue; } if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) { definition.setScope(defaultScope); } /* * 若是不是單例模式,默認是 * 將 BeanDefinition 在封裝一層進行註冊 */ if (!definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName()); } registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } }
獲取 BeanDefinition 註冊表,而後開始遍歷
獲取對應的 Bean 的 Class 名稱,也就是 Mapper 接口的 Class 對象
往構造方法的參數列表中添加一個參數,爲當前 Mapper 接口的名稱,由於 MapperFactoryBean 的構造方法的入參就是 Mapper 接口
修改該 Mapper 接口的 Class 對象 爲 MapperFactoryBean
,根據第3
步則會爲該 Mapper 接口建立一個對應的 MapperFactoryBean
對象了
添加 addToConfig
屬性,Mapper 是否添加到 Configuration 中
開始添加 sqlSessionFactory
或者 sqlSessionTemplate
屬性
若是設置了 sqlSessionFactoryBeanName,則添加 sqlSessionFactory 屬性,實際上配置的是 SqlSessionFactoryBean 對象,
不然,若是配置了 sqlSessionFactory 對象,則添加 sqlSessionFactory 屬性
在 SqlSessionDaoSupport
的 setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
方法中你會發現建立的就是SqlSessionTemplate
對象
若是配置了 sqlSessionTemplateBeanName,則添加 sqlSessionTemplate 屬性
不然,若是配置了 sqlSessionTemplate 對象,則添加 sqlSessionTemplate 屬性
上面沒有找到對應的 SqlSessionFactory,則設置經過類型注入
該方法的處理邏輯大體如上描述,主要作了一下幾個事:
將 Mapper 接口的 BeanDefinition 對象的 beanClass 屬性修改爲了MapperFactoryBean
的 Class 對象
添加了一個入參爲 Mapper 接口,這樣初始化的 Spring Bean 就是該 Mapper 接口對應的 MapperFactoryBean
對象了
添加MapperFactoryBean
對象的sqlSessionTemplate
屬性
org.mybatis.spring.annotation.@MapperScan
註解,指定須要掃描的包,將包中符合條件的 Mapper 接口,註冊成 beanClass
爲 MapperFactoryBean 的 BeanDefinition 對象,從而實現建立 Mapper 對象
咱們代碼以下:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends Annotation> annotationClass() default Annotation.class; Class<?> markerInterface() default Class.class; String sqlSessionTemplateRef() default ""; String sqlSessionFactoryRef() default ""; Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class; String lazyInitialization() default ""; String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT; }
value
和basePackage
都是指定 Mapper 接口的包路徑@Import(MapperScannerRegistrar.class)
,該註解負責資源的導入,若是導入的是一個 Java 類,例如此處爲 MapperScannerRegistrar 類,Spring 會將其註冊成一個 Bean 對象org.mybatis.spring.annotation.MapperScannerRegistrar
:實現 ImportBeanDefinitionRegistrar、ResourceLoaderAware 接口
做爲@MapperScann
註解的註冊器,根據註解信息註冊一個 MapperScannerConfigurer
對象,用於掃描 Mapper 接口
registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
方法
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 得到 @MapperScan 註解信息 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, // 生成 Bean 的名稱,'org.springframework.core.type.AnnotationMetadata#MapperScannerRegistrar#0' generateBaseBeanName(importingClassMetadata, 0)); } }
generateBaseBeanName
方法,爲MapperScannerConfigurer
生成一個beanName:org.springframework.core.type.AnnotationMetadata#MapperScannerRegistrar#0
registerBeanDefinitions
重載方法,註冊一個類型爲MapperScannerConfigurer
的 BeanDefinition 對象registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)
方法
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { // 建立一個 BeanDefinition 構建器,用於構建 MapperScannerConfigurer 的 BeanDefinition 對象 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); // 添加是否處理屬性中的佔位符屬性 builder.addPropertyValue("processPropertyPlaceHolders", true); /* * 依次添加註解中的配置屬性 */ Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { builder.addPropertyValue("annotationClass", annotationClass); } Class<?> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { builder.addPropertyValue("markerInterface", markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass)); } Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef"); if (StringUtils.hasText(sqlSessionTemplateRef)) { builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef")); } String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef"); if (StringUtils.hasText(sqlSessionFactoryRef)) { builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef")); } /* * 獲取到配置的 Mapper 接口的包路徑 */ List<String> basePackages = new ArrayList<>(); basePackages.addAll( Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList())); basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText) .collect(Collectors.toList())); basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName) .collect(Collectors.toList())); /* * 若是沒有 Mapper 接口的包路徑,則默認使用註解類所在的包路徑 */ if (basePackages.isEmpty()) { basePackages.add(getDefaultBasePackage(annoMeta)); } String lazyInitialization = annoAttrs.getString("lazyInitialization"); if (StringUtils.hasText(lazyInitialization)) { builder.addPropertyValue("lazyInitialization", lazyInitialization); } String defaultScope = annoAttrs.getString("defaultScope"); if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) { builder.addPropertyValue("defaultScope", defaultScope); } // 添加 Mapper 接口的包路徑屬性 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); // 往 BeanDefinitionRegistry 註冊表註冊 MapperScannerConfigurer 對應的 BeanDefinition 對象 registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }
MapperScannerConfigurer
的 BeanDefinition 對象processPropertyPlaceHolders
@MapperScan
註解中的配置屬性,例如:sqlSessionFactoryBeanName
和basePackages
MapperScannerConfigurer
類型的 BeanDefinition 對象這樣在 Spring 容器初始化的過程當中,會建立一個 MapperScannerConfigurer 對象,而後回到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法中,對包路徑下的 Mapper 接口進行解析,前面已經分析過了
MapperScannerRegistrar的內部類,代碼以下:
static class RepeatingRegistrar extends MapperScannerRegistrar { /** * {@inheritDoc} */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScansAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName())); if (mapperScansAttrs != null) { // 獲取 MapperScan 註解數組 AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value"); /* * 依次處理每一個 MapperScan 註解 */ for (int i = 0; i < annotations.length; i++) { registerBeanDefinitions(importingClassMetadata, annotations[i], registry, generateBaseBeanName(importingClassMetadata, i)); } } } }
@MapperScan
註解上面的@Repeatable(MapperScans.class)
信息,能夠看到若是同一個類上面定義多個@MapperScan
註解,則會生成對應的@MapperScans
註解@MapperScans
註解,依次處理@MapperScan
註解的信息除了配置 MapperScannerConfigurer 對象和經過 @MapperScan 註解掃描 Mapper 接口之外,咱們還能夠經過 MyBatis 提供的 scan
標籤來掃描 Mapper 接口
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
在 META-INF/spring.schemas
定義以下:
http\://mybatis.org/schema/mybatis-spring-1.2.xsd=org/mybatis/spring/config/mybatis-spring.xsd http\://mybatis.org/schema/mybatis-spring.xsd=org/mybatis/spring/config/mybatis-spring.xsd
http://mybatis.org/schema/mybatis-spring-1.2.xsd
或 http://mybatis.org/schema/mybatis-spring.xsd
在 META-INF/spring.handlers
定義以下:
http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler
org.mybatis.spring.config.NamespaceHandler
:繼承 NamespaceHandlerSupport 抽象類,MyBatis 的 XML Namespace 的處理器,代碼以下:
public class NamespaceHandler extends NamespaceHandlerSupport { /** * {@inheritDoc} */ @Override public void init() { registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser()); } }
<mybatis:scan />
標籤,使用 MapperScannerBeanDefinitionParser 解析org.mybatis.spring.config.MapperScannerBeanDefinitionParser
:實現 BeanDefinitionParser 接口,<mybatis:scan />
的解析器,代碼以下:
public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionParser { private static final String ATTRIBUTE_BASE_PACKAGE = "base-package"; private static final String ATTRIBUTE_ANNOTATION = "annotation"; private static final String ATTRIBUTE_MARKER_INTERFACE = "marker-interface"; private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator"; private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref"; private static final String ATTRIBUTE_FACTORY_REF = "factory-ref"; private static final String ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS = "mapper-factory-bean-class"; private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization"; private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope"; /** * 這個方法和 {@link MapperScannerRegistrar} 同樣的做用 * 解析 <mybatis:scan /> 標籤中的配置信息,設置到 MapperScannerConfigurer 的 BeanDefinition 對象中 * {@inheritDoc} * * @since 2.0.2 */ @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { // 建立一個 BeanDefinition 構建器,用於構建 MapperScannerConfigurer 的 BeanDefinition 對象 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); // 添加是否處理屬性中的佔位符屬性 builder.addPropertyValue("processPropertyPlaceHolders", true); /* * 解析 `scan` 標籤中的配置,添加到 BeanDefinition 中 */ try { String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION); if (StringUtils.hasText(annotationClassName)) { @SuppressWarnings("unchecked") Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) classLoader .loadClass(annotationClassName); builder.addPropertyValue("annotationClass", annotationClass); } String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE); if (StringUtils.hasText(markerInterfaceClassName)) { Class<?> markerInterface = classLoader.loadClass(markerInterfaceClassName); builder.addPropertyValue("markerInterface", markerInterface); } String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR); if (StringUtils.hasText(nameGeneratorClassName)) { Class<?> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName); BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class); builder.addPropertyValue("nameGenerator", nameGenerator); } String mapperFactoryBeanClassName = element.getAttribute(ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS); if (StringUtils.hasText(mapperFactoryBeanClassName)) { @SuppressWarnings("unchecked") Class<? extends MapperFactoryBean> mapperFactoryBeanClass = (Class<? extends MapperFactoryBean>) classLoader .loadClass(mapperFactoryBeanClassName); builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } } catch (Exception ex) { XmlReaderContext readerContext = parserContext.getReaderContext(); readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause()); } builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF)); builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF)); builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION)); builder.addPropertyValue("defaultScope", element.getAttribute(ATTRIBUTE_DEFAULT_SCOPE)); builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE)); return builder.getBeanDefinition(); } /** * {@inheritDoc} * * @since 2.0.2 */ @Override protected boolean shouldGenerateIdAsFallback() { return true; } }
<mybatis:scan />
標籤中的配置信息org.mybatis.spring.SqlSessionTemplate
:實現 SqlSession 和 DisposableBean 接口,SqlSession 操做模板實現類
實際上,代碼實現和 org.apache.ibatis.session.SqlSessionManager
類似,承擔 SqlSessionFactory 和 SqlSession 的職責
public class SqlSessionTemplate implements SqlSession, DisposableBean { /** * a factory of SqlSession */ private final SqlSessionFactory sqlSessionFactory; /** * {@link Configuration} 中默認的 Executor 執行器類型,默認 SIMPLE */ private final ExecutorType executorType; /** * SqlSessionInterceptor 代理對象 */ private final SqlSession sqlSessionProxy; /** * 異常轉換器,MyBatisExceptionTranslator 對象 */ private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); } public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true)); } 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; // 建立一個 SqlSession 的動態代理對象,代理類爲 SqlSessionInterceptor this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } }
sqlSessionFactory
:用於建立 SqlSession 對象executorType
:執行器類型,建立 SqlSession 對象時根據它建立對應的 Executor 執行器,默認爲sqlSessionProxy
:SqlSession 的動態代理對象,代理類爲 SqlSessionInterceptor
exceptionTranslator
:異常轉換器在調用SqlSessionTemplate中的SqlSession相關方法時,內部都是直接調用sqlSessionProxy
動態代理對象的方法,咱們來看看是如何處理的
SqlSessionTemplate的內部類,實現了 InvocationHandler 接口,做爲sqlSessionProxy
動態代理對象的代理類,對 SqlSession 的相關方法進行加強
代碼以下:
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // <1> 獲取一個 SqlSession 對象 SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 執行 SqlSession 的方法 Object result = method.invoke(sqlSession, args); // 當前 SqlSession 不處於 Spring 託管的事務中 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 // 關閉 SqlSession 會話,釋放資源 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; // 對異常進行轉換,差很少就是轉換成 MyBatis 的異常 Throwable translated = SqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
SqlSessionUtils
的getSqlSession
方法,獲取一個 SqlSession 對象SqlSessionUtils
的closeSqlSession
方法,「關閉」SqlSession 對象,這裏的關閉不是真正的關閉org.mybatis.spring.SqlSessionHolder
:繼承 org.springframework.transaction.support.ResourceHolderSupport
抽象類,SqlSession 持有器,用於保存當前 SqlSession 對象,保存到 org.springframework.transaction.support.TransactionSynchronizationManager
中,代碼以下:
public final class SqlSessionHolder extends ResourceHolderSupport { /** * SqlSession 對象 */ private final SqlSession sqlSession; /** * 執行器類型 */ private final ExecutorType executorType; /** * PersistenceExceptionTranslator 對象 */ private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionHolder(SqlSession sqlSession, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSession, "SqlSession must not be null"); notNull(executorType, "ExecutorType must not be null"); this.sqlSession = sqlSession; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; } }
org.mybatis.spring.SqlSessionUtils
:SqlSession 工具類,負責處理 MyBatis SqlSession 的生命週期,藉助 Spring 的 TransactionSynchronizationManager 事務管理器管理 SqlSession 對象
getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)
方法,註釋以下:
Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of current transaction.
If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the transaction if Spring TX is active and SpringManagedTransactionFactory is configured as a transaction manager.
從事務管理器(線程安全)中獲取一個 SqlSession 對象,若是不存在則建立一個 SqlSession,而後註冊到事務管理器中,方法以下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); // 從 Spring 事務管理器中獲取一個 SqlSessionHolder 對象 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); // 獲取到 SqlSession 對象 SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession"); // 上面沒有獲取到,則建立一個 SqlSession session = sessionFactory.openSession(executorType); // 將上面建立的 SqlSession 封裝成 SqlSessionHolder,往 Spring 事務管理器註冊 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
從 Spring 事務管理器中,根據 SqlSessionFactory 獲取一個 SqlSessionHolder 對象
調用 sessionHolder
方法,獲取到 SqlSession 對象,方法以下
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) { SqlSession session = null; if (holder != null && holder.isSynchronizedWithTransaction()) { // 若是執行器類型發生了變動,拋出 TransientDataAccessResourceException 異常 if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException( "Cannot change the ExecutorType when there is an existing transaction"); } // 增長計數,關閉 SqlSession 時使用 holder.requested(); LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction"); // 得到 SqlSession 對象 session = holder.getSqlSession(); } return session; }
若是 SqlSession 對象不爲 null,則直接返回,接下來會建立一個
上面沒有獲取到,則建立一個 SqlSession 對象
調用 registerSessionHolder
方法,將上面建立的 SqlSession 封裝成 SqlSessionHolder,往 Spring 事務管理器註冊
返回新建立的 SqlSession 對象
registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session)
方法
註釋以下:
Register session holder if synchronization is active (i.e. a Spring TX is active).
Note: The DataSource used by the Environment should be synchronized with the transaction either through DataSourceTxMgr or another tx synchronization.
Further assume that if an exception is thrown, whatever started the transaction will handle closing / rolling back the Connection associated with the SqlSession.
若是事務管理器處於激活狀態,則將 SqlSession 封裝成 SqlSessionHolder 對象註冊到其中,方法以下:
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); // <1> 若是使用 Spring 事務管理器 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]"); // <1.1> 建立 SqlSessionHolder 對象 holder = new SqlSessionHolder(session, executorType, exceptionTranslator); // <1.2> 綁定到 TransactionSynchronizationManager 中 TransactionSynchronizationManager.bindResource(sessionFactory, holder); // <1.3> 建立 SqlSessionSynchronization 到 TransactionSynchronizationManager 中 TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); // <1.4> 設置同步 holder.setSynchronizedWithTransaction(true); // <1.5> 增長計數 holder.requested(); } else { // <2> 若是非 Spring 事務管理器,拋出 TransientDataAccessResourceException 異常 if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } }
holder
SqlSessionSynchronization
對象(事務同步器)到 TransactionSynchronizationManager 中holder
的 synchronizedWithTransaction
屬性爲ture,和事務綁定了holder
的 referenceCount
引用數量closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
方法,註釋以下:
Checks if SqlSession passed as an argument is managed by Spring TransactionSynchronizationManager
If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call the close callback when the managed transaction ends
若是 SqlSessionFactory 是由 Spring 的事務管理器管理,而且和入參中的 session
相同,那麼只進行釋放,也就是將 referenceCount
引用數量減一,不然就直接關閉了
方法以下:
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); // <1> 從 TransactionSynchronizationManager 中,得到 SqlSessionHolder 對象 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); // <2.1> 若是相等,說明在 Spring 託管的事務中,則釋放 holder 計數 if ((holder != null) && (holder.getSqlSession() == session)) { LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]"); holder.released(); } else { // <2.2> 若是不相等,說明不在 Spring 託管的事務中,直接關閉 SqlSession 對象 LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]"); session.close(); } }
isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory)
方法,判斷 SqlSession 對象是否被 Sping 的事務管理器管理,代碼以下:
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); // 從 TransactionSynchronizationManager 中,得到 SqlSessionHolder 對象 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); // 若是相等,說明在 Spring 託管的事務中 return (holder != null) && (holder.getSqlSession() == session); }
org.mybatis.spring.SqlSessionUtils
的內部類,繼承了 TransactionSynchronizationAdapter 抽象類,SqlSession 的事務同步器,基於 Spring Transaction 體系
註釋以下:
Callback for cleaning up resources.
It cleans TransactionSynchronizationManager and also commits and closes the SqlSession.
It assumes that Connection life cycle will be managed by DataSourceTransactionManager or JtaTransactionManager
回調的時候清理資源
private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter { private final SqlSessionHolder holder; private final SqlSessionFactory sessionFactory; /** * 是否開啓 */ private boolean holderActive = true; public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) { notNull(holder, "Parameter 'holder' must be not null"); notNull(sessionFactory, "Parameter 'sessionFactory' must be not null"); this.holder = holder; this.sessionFactory = sessionFactory; } }
@Override public int getOrder() { // order right before any Connection synchronization return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1; }
當事務掛起時,取消當前線程的綁定的 SqlSessionHolder 對象,方法以下:
@Override public void suspend() { if (this.holderActive) { LOGGER.debug(() -> "Transaction synchronization suspending SqlSession [" + this.holder.getSqlSession() + "]"); TransactionSynchronizationManager.unbindResource(this.sessionFactory); } }
當事務恢復時,從新綁定當前線程的 SqlSessionHolder 對象,方法以下:
@Override public void resume() { if (this.holderActive) { LOGGER.debug(() -> "Transaction synchronization resuming SqlSession [" + this.holder.getSqlSession() + "]"); TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder); } }
在事務提交以前,調用 SqlSession#commit() 方法以前,提交事務。雖說,Spring 自身也會調用 Connection#commit() 方法,進行事務的提交。可是,SqlSession#commit() 方法中,不只僅有事務的提交,還有提交批量操做,刷新本地緩存等等,方法以下:
@Override public void beforeCommit(boolean readOnly) { // Connection commit or rollback will be handled by ConnectionSynchronization or DataSourceTransactionManager. // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so they are actually executed. // SpringManagedTransaction will no-op the commit over the jdbc connection // TODO This updates 2nd level caches but the tx may be rolledback later on! if (TransactionSynchronizationManager.isActualTransactionActive()) { try { LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]"); // 提交事務 this.holder.getSqlSession().commit(); } catch (PersistenceException p) { // 若是發生異常,則進行轉換,並拋出異常 if (this.holder.getPersistenceExceptionTranslator() != null) { DataAccessException translated = this.holder.getPersistenceExceptionTranslator() .translateExceptionIfPossible(p); if (translated != null) { throw translated; } } throw p; } } }
提交事務完成以前,關閉 SqlSession 對象,在 beforeCommit 以後調用,方法以下:
@Override public void beforeCompletion() { // Issue #18 Close SqlSession and deregister it now // because afterCompletion may be called from a different thread if (!this.holder.isOpen()) { LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); // 取消當前線程的綁定的 SqlSessionHolder 對象 TransactionSynchronizationManager.unbindResource(sessionFactory); // 標記無效 this.holderActive = false; LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); // 關閉 SqlSession 對象 this.holder.getSqlSession().close(); } }
在事務完成以後,關閉 SqlSession 對象,解決可能出現的跨線程的狀況,方法以下:
@Override public void afterCompletion(int status) { if (this.holderActive) { // 處於有效狀態 // afterCompletion may have been called from a different thread // so avoid failing if there is nothing in this one LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); // 取消當前線程的綁定的 SqlSessionHolder 對象 TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory); // 標記無效 this.holderActive = false; LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); // 關閉 SqlSession 對象 this.holder.getSqlSession().close(); } this.holder.reset(); }
還有一部份內容在org.mybatis.spring.batch
包路徑下,基於 Spring Batch 框架,Spring 和 MyBatis 的批處理進行集成,感興趣的小夥伴能夠去閱讀一下
我一般都是經過配置示例中方式配置MyBatis的,由於我以爲配置文件易於維護,比較可觀,固然也經過註解(@MapperScan
)的方式進行配置,原理相同
首先配置DataSource
數據源的 Sping Bean,咱們一般不會使用 MyBatis 自帶的數據源,由於其性能很差,都是經過Druid
或者HikariCP
等第三方組件來實現
配置SqlSessionFactoryBean
的 Spring Bean,設置數據源屬性dataSource
,還能夠配置configLocation
(mybatis-config.xml配置文件的路徑)、mapperLocations
(XML映射文件的路徑)等屬性,這樣讓 Spring 和 MyBatis 完美的整合到一塊兒了
配置MapperScannerConfigurer
的 Spring Bean,設置basePackage
(須要掃描的Mapper接口的路徑)、sqlSessionFactoryBeanName
(上面定義的SqlSessionFactoryBean)等屬性
由於實現了 BeanDefinitionRegistryPostProcessor
接口,在這些 Mapper 接口的 BeanDefinition 對象(Spring Bean 的前身)註冊完畢後,能夠進行一些處理
在這裏會修改這些 BeanDefinition 對象爲 MapperFactoryBean
類型,在初始化 Spring Bean 的過程當中則建立的是 MapperFactoryBean 對象,注入該對象則會調用其 getObject()
方法,返回的該 Mapper 接口對應的動態代理對象
這樣當你注入 Mapper 接口時,實際注入的是其動態代理對象
在SqlSessionTemplate
對象中,承擔 SqlSessionFactory 和 SqlSession 的職責
到這裏,相信你們對 MyBatis 集成到 Spring 的方案有了必定的瞭解,感謝你們的閱讀!!!😄😄😄
參考文章:芋道源碼《精盡 MyBatis 源碼分析》