前言
这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到了2次,2次都是非常重要的面试环节,因此自己印象很深刻。 这个题目我很早就深入学习了,但是一直没有整理出来,刚好最近一段时间由于工作太忙,大概有半年没有技术文章产出,因此趁着五一有点时间,整理了下分享给大家。 另外,估计不少同学应该也注意到了,DAO 接口的全路径名和 XML 文件中的 SQL 的 namespace + id 是一样的。其实,这也是建立关联的根本原因。 本文中的源码使用当前最新的版本,即:mybatis-spring 为 2.0.4,mybatis 为 3.5.4,引入这2个 jar 包即可查看到本文的所有代码。正文
当一个项目中使用了 Spring 和 Mybatis 时,通常会有以下配置。当然现在很多项目应该都是 SpringBoot 了,可能没有以下配置,但是究其底层原理都是类似的,无非是将扫描 bean 等一些工作通过注解来实现。<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.joonwhee.open.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:config/mapper/*.xml"/> <property name="configLocation" value="classpath:config/mybatis/mybatis-config.xml"/> <property name="typeAliasesPackage" value="com.joonwhee.open.po"/>bean> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/>bean>
通常我们还会有 DAO 类和 对用的 mapper 文件,如下。
package com.joonwhee.open.mapper; import com.joonwhee.open.po.UserPO; public interface UserPOMapper {
UserPO queryByPrimaryKey(Integer id);}
<?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.joonwhee.open.mapper.UserPOMapper" > <resultMap id="BaseResultMap" type="com.joonwhee.open.po.UserPO"> <result column="id" property="id" jdbcType="INTEGER" /> <result column="name" property="name" jdbcType="VARCHAR" /> resultMap> <select id="queryByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer"> select id, name from user where id = #{id,jdbcType=INTEGER} select>mapper>
1、解析 MapperScannerConfigurer
MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor,会在 Spring 构建 IoC容器的早期被调用重写的 postProcessBeanDefinitionRegistry 方法,参考:Spring IoC:invokeBeanFactoryPostProcessors 详解@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders(); } // 1.新建一个ClassPathMapperScanner,并填充相应属性 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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) {
// 2.设置mapper bean是否需要懒加载 scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } // 3.注册Filter,因为上面构造函数我们没有使用默认的Filter, // 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的 scanner.registerFilters(); // 4.扫描basePackage,basePackage可通过",; \t\n"来填写多个, // ClassPathMapperScanner重写了doScan方法 scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}
3.注册 Filter,见代码块1。 4.扫描 basePackage,这边会走到 ClassPathBeanDefinitionScanner(ClassPathMapperScanner 的父类),然后在执行 “doScan(basePackages)” 时回到 ClassPathMapperScanner 重写的方法,见代码块2。
代码块1:registerFilters
public void registerFilters() {
boolean acceptAllInterfaces = true; // if specified, use the given annotation and / or marker interface // 1.如果指定了注解,则将注解添加到includeFilters if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // override AssignableTypeFilter to ignore matches on the actual marker interface // 2.如果指定了标记接口,则将标记接口添加到includeFilters, // 但这边重写了matchClassName方法,并返回了false, // 相当于忽略了标记接口上的匹配项,所以该参数目前相当于没有任何作用 if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {