本篇是繼上篇MyBatis原理歸納延伸的,因此若是有小夥伴還沒看上篇博文的話,能夠先去看下,也不會浪費你們太多的時間,由於本篇會結合到上篇敘述的相關內容。spring
好,切入正題,這篇主要講一個點,就是咱們在結合spring去使用mybatis的時候,spring爲咱們作了什麼事。仍是老套路,咱們只講過程思路,具體細節還望各位小夥伴找時間去研究,若是我全講了,大家也都看懂了,那大家最多也就是感到一種得到感,而不是成就感,得到感是會隨着時間的推移而慢慢減小的,因此我這裏主要提供給你們一個思路,而後你們能夠順着這條思路慢慢摸索下去,從而得到成就感!sql
MyBatis-Spring 會幫助你將 MyBatis 代碼無縫地整合到 Spring 中。 使用這個類庫中的類, Spring 將會加載必要的 MyBatis 工廠類和 session 類。 這個類庫也提供一個簡單的方式來注入 MyBatis 數據映射器和 SqlSession 到業務層的 bean 中。 並且它也會處理事務, 翻譯 MyBatis 的異常到 Spring 的 DataAccessException 異常(數據訪問異常,譯者注)中。最終,它並 不會依賴於 MyBatis,Spring 或 MyBatis-Spring 來構建應用程序代碼。(這是官網解釋)apache
通常狀況下,咱們使用xml的形式引入mybatis,通常的配置以下:segmentfault
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> <!-- 初始化鏈接大小 --> <property name="initialSize" value="${initialSize}"></property> <!-- 鏈接池最大數量 --> <property name="maxActive" value="${maxActive}"></property> <!-- 鏈接池最大空閒 --> <property name="maxIdle" value="${maxIdle}"></property> <!-- 鏈接池最小空閒 --> <property name="minIdle" value="${minIdle}"></property> <!-- 獲取鏈接最大等待時間 --> <property name="maxWait" value="${maxWait}"></property> </bean> <!-- spring和MyBatis的完美結合,不須要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 自動掃描mapping.xml文件 --> <property name="mapperLocations" value="classpath:com/javen/mapping/*.xml"></property> </bean> <!-- DAO接口所在包名,Spring會自動查找其下的類 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.javen.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>
如上配置所示,咱們通常須要申明dataSource、sqlSessionFactory以及MapperScannerConfigurer。如何咱們還有其餘mybatis的配置,好比plugin、typehandler等,咱們能夠另外申明一個mybaits-config.xml文件,在sqlSessionFactory配置中引入便可。下面對各部分做用總結下。
dataSource:申明一個數據源;
sqlSessionFactory:申明一個sqlSession的工廠;
MapperScannerConfigurer:讓spring自動掃描咱們持久層的接口從而自動構建代理類。session
註解形式的話至關於將上述的xml配置一一對應成註解的形式mybatis
@Configuration @MapperScan(value="org.fhp.springmybatis.dao") public class DaoConfig { @Value("${jdbc.driverClass}") private String driverClass; @Value("${jdbc.user}") private String user; @Value("${jdbc.password}") private String password; @Value("${jdbc.jdbcUrl}") private String jdbcUrl; @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driverClass); dataSource.setUsername(user); dataSource.setPassword(password); dataSource.setUrl(jdbcUrl); return dataSource; } @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); return sessionFactory.getObject(); } }
很明顯,同樣須要一個dataSource,SqlSessionFactory以及一個@MapperScan的註解。這個註解的做用跟上述的
MapperScannerConfigurer的做用是同樣的。app
在講mybatis如何無縫整合進spring以前,咱們先認識下BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar這兩個接口的做用。ide
咱們先看下這兩個接口是什麼樣的。post
//BeanDefinitionRegistryPostProcessor接口 public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException; } //ImportBeanDefinitionRegistrar接口 public interface ImportBeanDefinitionRegistrar { void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2); }
對於這兩個接口咱們先看官方文檔給咱們的解釋。ui
如下是BeanDefinitionRegistryPostProcessor的解釋:
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in. In particular, BeanDefinitionRegistryPostProcessor may register further bean definitions which in turn define BeanFactoryPostProcessor instances.
意思大概就是咱們能夠擴展spring對於bean definitions的定義。也就是說可讓咱們實現自定義的註冊bean定義的邏輯。
再來看下ImportBeanDefinitionRegistrar的解釋:
public interface ImportBeanDefinitionRegistrar Interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary. Along with @Configuration and ImportSelector, classes of this type may be provided to the @Import annotation (or may also be returned from an ImportSelector).
通俗解釋來說就是在@Configuration上使用@Import時能夠自定義beanDefinition,或者做爲ImportSelector接口的返回值(有興趣的小夥伴能夠自行研究)。
因此總結下就是若是我想擴展beanDefinition那麼我能夠繼承這兩個接口實現。下面咱們就從mybatis配置方式入手講講spring和mybatis是如何無縫整合的。
首先,容器啓動的時候,咱們在xml配置中的SqlSessionFactoryBean會被初始化,因此咱們先看下SqlSessionFactoryBean是在初始化的時候做了哪些工做。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class); private Resource configLocation; private Configuration configuration; private Resource[] mapperLocations; private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; private String environment = SqlSessionFactoryBean.class.getSimpleName(); private boolean failFast; private Interceptor[] plugins; private TypeHandler<?>[] typeHandlers; private String typeHandlersPackage; private Class<?>[] typeAliases; private String typeAliasesPackage; private Class<?> typeAliasesSuperType; private DatabaseIdProvider databaseIdProvider; private Class<? extends VFS> vfs; private Cache cache; private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory; public SqlSessionFactoryBean() { } ... }
咱們能夠看到這個類實現了FactoryBean、InitializingBean和ApplicationListener接口,對應的接口在bean初始化的時候爲執行些特定的方法(若是不清楚的小夥伴請自行百度,這裏不做過多敘述)。如今咱們來看看都有哪些方法會被執行,這些方法又做了哪些工做。
//FactoryBean 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(); } //ApplicationListener public void onApplicationEvent(ApplicationEvent event) { if (this.failFast && event instanceof ContextRefreshedEvent) { this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } }
經過觀察代碼咱們能夠知道前面兩個都是在作同一件事情,那就是在構建sqlSessionFactory,在構建sqlSessionFactory時mybatis會去解析配置文件,構建configuation。後面的onApplicationEvent主要是監聽應用事件時作的一些事情(不詳講,有興趣的同窗能夠本身去了解下)。
其次,咱們回憶下咱們在xml配置中還配置了MapperScannerConfigurer,或者也能夠配置多個的MapperFactoryBean,道理都是同樣的,只是MapperScannerConfigurer幫咱們封裝了這一個過程,能夠實現自動掃描指定包下的mapper接口構建MapperFactoryBean。
問題1:爲何咱們從spring容器中能直接獲取對應mapper接口的實現類?而不用使用sqlSession去getMapper呢?
答案其實在上面就已經爲你們解答了,就是MapperFactoryBean。咱們先看看這個類。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; private boolean addToConfig = true; public MapperFactoryBean() { } public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } ... }
這個類繼承了SqlSessionDaoSupport,實現了FactoryBean。
咱們先講講SqlSessionDaoSupport這個類
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public SqlSessionDaoSupport() { } public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSession = sqlSessionTemplate; this.externalSqlSession = true; } public SqlSession getSqlSession() { return this.sqlSession; } protected void checkDaoConfig() { Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }
能夠看到這個類繼承了DaoSupport,咱們再來看下這個類。
public abstract class DaoSupport implements InitializingBean { protected final Log logger = LogFactory.getLog(this.getClass()); public DaoSupport() { } public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed", var2); } } protected abstract void checkDaoConfig() throws IllegalArgumentException; protected void initDao() throws Exception { } }
能夠看到實現了InitializingBean接口,因此在類初始化時爲執行afterPropertiesSet方法,咱們看到afterPropertiesSet方法裏面有checkDaoConfig方法和initDao方法,其中initDao是模板方法,提供子類自行實現相關dao初始化的操做,咱們看下checkDaoConfig方法做了什麼事。
//MapperFactoryBean protected void checkDaoConfig() { super.checkDaoConfig(); Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = this.getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { configuration.addMapper(this.mapperInterface); } catch (Exception var6) { this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6); throw new IllegalArgumentException(var6); } finally { ErrorContext.instance().reset(); } } }
這個方法具體的實現是在MapperFactoryBean類裏面的,主要做用就是對驗證mapperInterface是否存在configuration對象裏面。
而後咱們再來看下MapperFactoryBean實現了FactoryBean的目的是什麼。咱們都知道FactoryBean有一個方法是getObject,這個方法的做用就是在spring容器初始化bean時,若是判斷這個類是否繼承自FactoryBean,那麼在獲取真正的bean實例時會調用getObject,將getObject方法返回的值註冊到spring容器中。在明白了這些知識點以後,咱們看下MapperFactoryBean的getObject方法是如何實現的。
//MapperFactoryBean public T getObject() throws Exception { return this.getSqlSession().getMapper(this.mapperInterface); }
看到這裏是否就已經明白爲何在結合spring時咱們不須要使用sqlSession對象去獲取咱們的mapper實現類了吧。由於spring幫咱們做了封裝!
以後的操做能夠結合上面博文去看mybatis如何獲取到對應的Mapper對象的了。附上上篇博文地址:MyBatis原理歸納。
接下來咱們看下mybatis是如何結合spring構建MapperFactoryBean的beanDefinition的。這裏咱們須要看看MapperScannerConfigurer這個類,這個類的目的就是掃描咱們指定的dao層(持久層)對應的包(package),構建相應的beanDefinition提供給spring容器去實例化咱們的mapper接口對象。
//MapperScannerConfigurer public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { private String basePackage; private boolean addToConfig = true; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionFactoryBeanName; private String sqlSessionTemplateBeanName; private Class<? extends Annotation> annotationClass; private Class<?> markerInterface; private ApplicationContext applicationContext; private String beanName; private boolean processPropertyPlaceHolders; private BeanNameGenerator nameGenerator; public MapperScannerConfigurer() { } ... }
經過代碼,咱們能夠看到這個類實現了BeanDefinitionRegistryPostProcessor這個接口,經過前面對BeanDefinitionRegistryPostProcessor的講解,咱們去看看MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法的實現。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { this.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, ",; \t\n")); }
能夠看這裏就是在構建ClassPathMapperScanner對象,而後調用scan方法掃描。接下來咱們繼續看這個掃描的操做,由於這個類繼承了ClassPathBeanDefinitionScanner,調用的scan方法是在ClassPathBeanDefinitionScanner裏申明的。
//ClassPathBeanDefinitionScanner public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); this.doScan(basePackages); if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; }
這裏咱們須要注意doScan這個方法,這個方法在ClassPathMapperScanner中重寫了。
//ClassPathMapperScanner public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { this.processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
這裏調用了父類的doScan獲得beanDefinitions的集合。這裏的父類的doScan方法是spring提供的包掃描操做,這裏不過多敘述,感興趣的小夥伴能夠自行研究。咱們還注意到在獲得beanDefinitions集合後,這裏還調用了processBeanDefinitions方法,這裏是對beanDefinition作了一些特殊的處理以知足mybaits的需求。咱們先來看下這個方法。
//ClassPathMapperScanner#doScan private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { Iterator var3 = beanDefinitions.iterator(); while(var3.hasNext()) { BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next(); GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition(); if (this.logger.isDebugEnabled()) { this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 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) { this.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) { this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { if (this.logger.isDebugEnabled()) { this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); } definition.setAutowireMode(2); } } }
這裏咱們注意到有這麼一行代碼:definition.setBeanClass(this.mapperFactoryBean.getClass()),看到這裏咱們就能夠知道爲何spring在加載初始化咱們的mapper接口對象會初始化成MapperFactoryBean對象了。
好了,到這裏咱們也就明白了spring是如何幫咱們加載註冊咱們的mapper接口對應的實現類了。對於代碼裏涉及到的其餘細節,這裏暫時不做過多講解,仍是老套路,只講解整體思路。
基於註解形式的配置其實就是將xml配置對應到註解中來,本質上的流程仍是同樣的。因此這裏我就簡單講講。咱們先看看MapperScannerRegistrar這個類,由於這個類是spring構建MapperFactoryBean的核心類。
//MapperScannerRegistrar public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { private ResourceLoader resourceLoader; public MapperScannerRegistrar() { } ... }
這裏咱們注意到MapperScannerRegistrar實現了ImportBeanDefinitionRegistrar接口,在前面的敘述中咱們已經知道了實現ImportBeanDefinitionRegistrar接口的做用是什麼了,因此咱們直接看看這裏具體作了什麼操做。
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); if (this.resourceLoader != null) { scanner.setResourceLoader(this.resourceLoader); } Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class<?> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass)); } Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass)); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList(); String[] var10 = annoAttrs.getStringArray("value"); int var11 = var10.length; int var12; String pkg; for(var12 = 0; var12 < var11; ++var12) { pkg = var10[var12]; if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } var10 = annoAttrs.getStringArray("basePackages"); var11 = var10.length; for(var12 = 0; var12 < var11; ++var12) { pkg = var10[var12]; if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } Class[] var14 = annoAttrs.getClassArray("basePackageClasses"); var11 = var14.length; for(var12 = 0; var12 < var11; ++var12) { Class<?> clazz = var14[var12]; basePackages.add(ClassUtils.getPackageName(clazz)); } scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages)); }
經過觀察咱們看到最後仍是調用了ClassPathMapperScanner的doScan去掃描指定包下的mapper接口(持久層),而後構建對應的beanDefinition類。前面咱們知道是經過MapperScan這個註解去指定包的,而後咱們也能夠看到,在這個方法一開始就取出這個註解的值,而後進行接下來的操做的。
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
以後的過程其實跟xml形式配置的同樣了。
好啦,這篇沒想囉理八嗦說了那麼多,可能有好多小夥伴看到最後也是懵逼狀態,這裏有個建議,打開IDE,邊看邊對着代碼跟蹤,若是哪裏以爲不對,能夠直接debug。
這裏給你們提個看源碼的建議,就是猜測+驗證。先猜測本身的想法,而後經過查找相關問題或者debug代碼去驗證本身的思路。
好啦,到這裏爲止,mybatis和spring-mybatis的基本原理都跟你們說了一遍,不知道小夥伴們有沒有收穫呢,下一篇,我會帶你們手寫一遍mybatis,是純手寫並且還能跑起來的那種哦!
注:本人不才,以上若有錯誤的地方或者不規範的敘述還望各位小夥伴批評指點。