MyBatis整合到Spring的原理

一、整合步骤

mybatis整合到spring一般配置如下

  1. 添加依赖

    以maven为例,在pom.xml中添加

	<dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis-spring</artifactId>
           <version>1.3.1</version>
       </dependency>
  1. 配置Spring的bean
  	<!--数据源,可选其他如Druid等-->
      <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"
            p:driver="com.mysql.jdbc.Driver"
            p:password="123456"
            p:url="jdbc:mysql://localhost:3306/mybatis"
            p:username="root"
      />
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
            p:dataSource-ref="dataSource"
            p:configLocation="classpath:mybatis-spring.xml"
            p:mapperLocations="classpath:tk/mybatis/simple/mapper/*.xml"
            p:typeAliasesPackage="tk.mybatis.simple.model"
      />
      <!--扫描mapper接口所在包-->
      <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
  		  p:processPropertyPlaceHolders="true"
            p:basePackage="tk.mybatis.simple.mapper"/>

整合就这么完成了,其中SqlSessionFactoryBean负责提供SqlSessionFactory,所以需要给它提供配置文件等相关信息;MapperScannerConfigurer是扫描指定包的接口,把它们转换成动态代理bean。

注:p:processPropertyPlaceHolders=“true"这句代码用于解析”${}“替换成实际的值,如果没有用到”${}",可省略。

二、源码分析

1. SqlSessionFactoryBean
在SqlSessionFactoryBean

其中实现了InitializingBean接口的bean,会在其初始化时调用afterPropertiesSet()方法

	public void afterPropertiesSet() throws Exception {
		//数据源不能为空
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
		//sqlSessionFactoryBuilder成员在类初始化时会实例化
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
		//configuration、configLocation不能同时配置
        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();
    }

调用了buildSqlSessionFactory()方法

	protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;
    XMLConfigBuilder xmlConfigBuilder = null;

    if (this.configuration != null) {
		//可在配置文件中配置configuration
      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) {
		//这种情况是在配置了mybatis配置文件所在位置
		//实例化xmlConfigBuilder,供后面解析
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      ...省略log
		//什么都没配置,就直接实例化一个Configuration
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }
    ...省略多个属性设置

    if (xmlConfigBuilder != null) {
      try {
		//进行Mybatis配置文件解析
        xmlConfigBuilder.parse();

        ...省略log
      } catch...
    }

    ...

    if (!isEmpty(this.mapperLocations)) {
		//配置了mapperLocations就对该路径下的mapper的xml文件进行解析
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch ...
      }
    } else {
      ..省略log
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

XMLConfigBuilder解析流程可参考:MyBatis源码笔记(一) – 大致流程
XMLMapperBuilder解析流程可参考:MyBatis源码笔记(三) – mapper解析流程

最终调用sqlSessionFactoryBuilder的build()方法进行构建

	public SqlSessionFactory build(Configuration config) {
		return new DefaultSqlSessionFactory(config);
	}

2. MapperScannerConfigurer
MapperScannerConfigurer
实现BeanDefinitionRegistryPostProcessor接口的bean在初始化时会调用postProcessBeanDefinitionRegistry()方法

	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		//处理${}
	    if (this.processPropertyPlaceHolders) {
	      processPropertyPlaceHolders();
	    }
		//【标记1】继承了Spring的ClassPathBeanDefinitionScanner,用于扫描类路径下的类、接口
	    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
		//addToConfig默认为true
	    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);
		//-----分割线-------
		//【标记2】
	    scanner.registerFilters();
		//【标记3】
	    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }

【标记1】

	public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
		//第二个false代表不使用默认的过滤器(默认的过滤器是过滤出带@Component、@Resource注解的类)
    	super(registry, false);
    }

【标记2】

	public void registerFilters() {
    boolean acceptAllInterfaces = true;
	
	    //如果配置了annotationClass,只扫描配置的特定的注解
	    if (this.annotationClass != null) {
	      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
	      acceptAllInterfaces = false;
	    }
	
	    // 如果配置了markerInterface,只扫描特定的接口
	    if (this.markerInterface != null) {
	      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
	        @Override
	        protected boolean matchClassName(String className) {
	          return false;
	        }
	      });
	      acceptAllInterfaces = false;
	    }
		//上面都没配置
	    if (acceptAllInterfaces) {
	      // 扫描所有接口
	      addIncludeFilter(new TypeFilter() {
	        @Override
	        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
	          return true;
	        }
	      });
	    }
	
	    // 排除 package-info.java
	    addExcludeFilter(new TypeFilter() {
	      @Override
	      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
	        String className = metadataReader.getClassMetadata().getClassName();
	        return className.endsWith("package-info");
	      }
	    });
    }

【标记3】

	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

	public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
	
	    if (beanDefinitions.isEmpty()) {
	      ...省略log警告
	    } else {
	      processBeanDefinitions(beanDefinitions);
	    }
	
	    return beanDefinitions;
   }

scan()方法是Spring原本提供的,而doScan方法被ClassPathMapperScanner覆盖重写了。
ClassPathBeanDefinitionScanner如何扫描包可参考:Spring component-scan源码分析(一)

主要分析processBeanDefinitions()方法

	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
		//遍历所有扫描到的BeanDefinition
	    GenericBeanDefinition definition;
	    for (BeanDefinitionHolder holder : beanDefinitions) {
	      definition = (GenericBeanDefinition) holder.getBeanDefinition();
			...省略log
	
	      //构造方法参数添加为当前接口的类名
	      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
		  //实际把bean的类型改为了MapperFactoryBean
	      definition.setBeanClass(this.mapperFactoryBean.getClass());
			//上面提到默认为true,表示把mapper接口添加到configuration中
	      definition.getPropertyValues().add("addToConfig", this.addToConfig);
			...
			//explicitFactoryUsed标志在外部配置文件指定了
		//sqlSessionFactory、sqlSessionFactoryBeanName、sqlSessionTemplateBeanName、sqlSessionTemplate其中一个时为true
	      if (!explicitFactoryUsed) {
	        ...省略log
			//【标记4】这句话表示当前bean会根据类型自动注入属性
	        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
	      }
	    }
    }

这里对扫描出来的beanDefinitions逐个处理,把实际类型改成了MapperFactoryBean类型,所以最后实例化出来的类型是MapperFactoryBean。
MapperFactoryBean

回顾下Mybatis拿到mapper实现类是通过
sqlSession.getMapper(xxx.class);而MapperFactoryBean是个工厂bean,那它的getObject()方法就需要返回对应mapper接口的实现类。

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

public SqlSession getSqlSession() {
   	return this.sqlSession;
}

如上,getObject()方法果然通过了sqlSession.getMapper(xxx.class)来拿到对应mapper接口的实现类,而getSqlSession()方法在SqlSessionDaoSupport类中。

三、最后补充

大致流程算是走通了,现在关注下细节。
上面的DaoSupport抽象类实现了InitializingBean接口,而在afterPropertiesSet()做的事情是

	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		//留给子类实现
		checkDaoConfig();
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

MapperFactoryBean类实现了该方法

	@Override
	protected void checkDaoConfig() {
		//调用父类
		super.checkDaoConfig();
		//mapperInterface是接口类型
		notNull(this.mapperInterface, "Property 'mapperInterface' is required");
		//addToConfig标志在这里起作用
		Configuration configuration = getSqlSession().getConfiguration();
		if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
		  try {
		    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();
		  }
		}
	}

父类SqlSessionDaoSupport的checkDaoConfig()方法

	protected void checkDaoConfig() {
    	notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  	}

这里要检测sqlSession成员不能为空,而该成员被赋值可能的地方是下面两个方法:

	public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
	    if (!this.externalSqlSession) {
	      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
	    }
  	}

	public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
		this.sqlSession = sqlSessionTemplate;
		this.externalSqlSession = true;
	}

但从上面的processBeanDefinitions()方法中可以看到,对beanDefinition的处理时只加了一个属性addToConfig,同时配置文件中的确是配了一个SqlSessionFactory的bean,那它们是怎么关联起来的呢???

答案是:【标记4】处代码,指定了MapperFactoryBean实例化时,会自动填充setter方法的属性,我们在容器中配置了SqlSessionFactory的bean,所以会调用setSqlSessionFactory()方法来注入属性。

这也是为什么在容器只有一个sqlSessionFactory实例的情况下,MapperScannerConfigurer在配置不用配置sqlSessionFactory等这些配置,全是AUTOWIRE_BY_TYPE的功劳

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值