文章目录

  • (一)Mybatis单独操作数据库程序
    • 1.1、数据库表
    • 1.2、建立PO
    • 1.3、建立mapper接口映射
    • 1.4、建立Mybatis配置文件
    • 1.5、建立mapper映射文件
    • 1.6、测试类
    • 1.7、Mybatis编写程序小结
  • (二)Spring中集成mybatis操作数据库程序
    • 2.1、编写spring配置文件
    • 2.2、mybatis全局策略配置文件
    • 2.3、建立mapper映射文件(与单独使用mybatis一致)
    • 2.4、建立mapper接口映射(与单独使用mybatis一致)
    • 2.5、测试类
    • 2.6、独立与集成在spring中的mybatis小结
  • (三)mybatis源码分析(集成于spring)
    • 3.1、SqlSessionFactoryBean配置SqlSessionFactory类型bean
      • 3.1.1、SqlSessionFactoryBean层级图
      • 3.1.2、SqlSessionFactoryBean初始化(InitializingBean接口)
      • 3.1.3、获取SqlSessionFactoryBean实例(FactoryBean接口)
    • 3.2、MapperFactoryBean配置业务类型bean
      • 3.2.1、MapperFactoryBean层级图
      • 3.2.2、MapperFactoryBean初始化(InitializingBean接口)
      • 3.2.3、MapperFactoryBean获取实例(FactroyBean接口)
    • 3.3、MapperScannerConfigurer批量配置映射器
      • 3.3.1、MapperScannerConfigurer层级图
        • (1)InitializingBean接口
        • (2)BeanFactoryPostProcessor接口
        • (3)BeanDefinitionRegistryPostProcessor接口
      • 3.3.2、processPropertyPlaceHolders属性
      • 3.3.3、根据配置生成过滤器
      • 3.3.4、扫描java文件

(一)Mybatis单独操作数据库程序

1.1、数据库表

CREATE TABLE `student` (`number` int(11) NOT NULL AUTO_INCREMENT COMMENT '学号',`name` varchar(5) DEFAULT NULL COMMENT '姓名',`major` varchar(30) DEFAULT NULL COMMENT '专业',PRIMARY KEY (`number`)
) ENGINE=InnoDB AUTO_INCREMENT=20180104 DEFAULT CHARSET=utf8 COMMENT='学生信息表';

1.2、建立PO

public class Student {private Integer number;private String name;private String major;//省略set和get方法以及构造函数
}

1.3、建立mapper接口映射

public interface StudentMybatisMapper {void insertStudent(Student student);Student getStudent(Integer number);
}

1.4、建立Mybatis配置文件

<?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><!-- mybatis环境配置 --><settings><!-- 开启自动驼峰命名规则(camel case)映射 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 开启延迟加载开关 --><setting name="lazyLoadingEnabled" value="true"/><!-- 将积极加载改为消极加载(即按需加载),默认值就是false --><setting name="aggressiveLazyLoading" value="false"/><!-- 打开全局缓存开关(二级环境),默认值是true --><setting name="cacheEnabled" value="false"/><setting name="defaultExecutorType" value="REUSE" /></settings><!-- 配置返回实体参数别名 --><typeAliases><typeAlias type="com.spring.model.Student" alias="Student"/></typeAliases><!-- 数据源配置 --><environments default="development"><environment id="development"><!-- 使用JDBC事务管理 --><transactionManager type="JDBC"/><!-- 数据库连接池 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- mapper映射文件导入 --><mappers><mapper resource="config/mapper/StudentMapper.xml"></mapper></mappers>
</configuration>

1.5、建立mapper映射文件

<?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接口 -->
<mapper namespace="com.spring.mapper.StudentMybatisMapper"><!-- id与mapper接口中方法名对应  parameterType是参数类型--><insert id="insertStudent" parameterType="Student">insert into student (name,major) values (#{name},#{major});</insert><!-- resultType是返回参数类型,正常为全限定类名,mybatis全局设置了别名,所以该处可以直接写别名 --><select id="getStudent" parameterType="java.lang.Integer" resultType="Student">select * from student where number = #{number}</select>
</mapper>

1.6、测试类

public static void main(String[] args) {Reader reader = null;try {reader = Resources.getResourceAsReader("config/spring-mybatis.xml");} catch (IOException e) {e.printStackTrace();}SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//构建SQLSessionFactorySqlSession sqlSession = sqlSessionFactory.openSession(true);//创建SQLSessionStudentMybatisMapper mapper = sqlSession.getMapper(StudentMybatisMapper.class);//获取连接映射Student student = new Student("王五","摄影专业");mapper.insertStudent(student);//调用保存方法Student result = mapper.getStudent(20180102);//调用查询方法System.out.println(result);//输出查询结果sqlSession.close();//关闭连接
}

输出结果:

Student{number=20180102, name='范统', major='计算机科学与工程'}

1.7、Mybatis编写程序小结

  • (1)创建数据库表;
  • (2)建立java实体PO;
  • (3)Mapper业务接口定义;
  • (4)编写mapper文件映射;
  • (5)mybatis配置数据源、执行环境参数、别名、mapper文件引入;
  • (6)创建SqlSessionFactory、SqlSession和获取数据库连接,集成到spring中时会自动执行,不需要手动去编写。

(二)Spring中集成mybatis操作数据库程序

2.1、编写spring配置文件

applicationContext.xml配置文件中仅配置了数据源、业务bean的封装便于使用直接从getBean中获取,正常开发时只需要使用spring的常规注解即可获取,配置文件如下:

<?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" default-autowire="byName"><!-- 配置数据源--><bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/test" /><property name="username" value="root" /><property name="password" value="root" /><property name="maxIdle" value="30" /><property name="defaultAutoCommit" value="true" /><property name="removeAbandonedTimeout" value="60" /></bean><!-- 配置sqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation" value="classpath:config/spring-mybatis.xml" /><property name="dataSource" ref="dataSource"/></bean><!--配置业务bean并在spring中映射mapper--><bean id="studentMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.spring.mapper.StudentMybatisMapper" /><property name="sqlSessionFactory" ref="sqlSessionFactory" /></bean>
</beans>

2.2、mybatis全局策略配置文件

<?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><!-- mybatis环境配置 --><settings><!-- 开启自动驼峰命名规则(camel case)映射 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 开启延迟加载开关 --><setting name="lazyLoadingEnabled" value="true"/><!-- 将积极加载改为消极加载(即按需加载),默认值就是false --><setting name="aggressiveLazyLoading" value="false"/><!-- 打开全局缓存开关(二级环境),默认值是true --><setting name="cacheEnabled" value="false"/><setting name="defaultExecutorType" value="REUSE" /></settings><!-- 配置返回实体参数别名 --><typeAliases><typeAlias type="com.spring.model.Student" alias="Student"/></typeAliases><!-- 该mapper文件引入可以单独引入,也可批量引入 --><mappers><mapper resource="config/mapper/StudentMapper.xml"></mapper></mappers>
</configuration>

2.3、建立mapper映射文件(与单独使用mybatis一致)

<?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接口 -->
<mapper namespace="com.spring.mapper.StudentMybatisMapper"><!-- id与mapper接口中方法名对应  parameterType是参数类型--><insert id="insertStudent" parameterType="Student">insert into student (name,major) values (#{name},#{major});</insert><!-- resultType是返回参数类型,正常为全限定类名,mybatis全局设置了别名,所以该处可以直接写别名 --><select id="getStudent" parameterType="java.lang.Integer" resultType="Student">select * from student where number = #{number}</select>
</mapper>

2.4、建立mapper接口映射(与单独使用mybatis一致)

public interface StudentMybatisMapper {void insertStudent(Student student);Student getStudent(Integer number);
}

2.5、测试类

public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("config/applicationContext.xml");//spring容器加载beanStudentMybatisMapper mapper = applicationContext.getBean("studentMapper",StudentMybatisMapper.class);//获取连接映射Student student = new Student("赵升","摄影专业");mapper.insertStudent(student);//调用保存方法Student result = mapper.getStudent(20180103);//调用查询方法System.out.println(result);
}

输出结果:

Student{number=20180103, name='史珍香', major='计算机科学与工程'}

2.6、独立与集成在spring中的mybatis小结

  • (1)独立使用mybatis时配置数据源是在自己的spring-mybatis.xml中,spring则是将数据源放入容器配置文件中并注入到bean中;
  • (2)spring中mybatis统一交由容器来管理,获得数据库连接直接从容器中获取,不需要自己手动去获取SqlSessionFactory、SqlSession等,避免了大量的创建、销毁过程和冗余代码;
  • (3)spring中可以直接舍弃调spring-mybatis.xml,在spring中可以sqlSessionFactory的bean中将其它属性也注入即可,比单独使用更简便。

(三)mybatis源码分析(集成于spring)

3.1、SqlSessionFactoryBean配置SqlSessionFactory类型bean

<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation" value="classpath:config/spring-mybatis.xml" /><property name="dataSource" ref="dataSource"/>
</bean>

Spring中配置sqlSessionFactory是通过org.mybatis.spring.SqlSessionFactoryBean来实现,所以关键点是SqlSessionFactoryBean封装了Mybatis的功能,接下来将对SqlSessionFactoryBean进行重点分析。

3.1.1、SqlSessionFactoryBean层级图

值得一提的是FactoryBeanInitializingBean接口类。

  • (1)FactoryBean:该接口在spring源码分析中多次使用,主要作用是实际业务bean实现该接口后,当使用getBean来获取bean时实际上是getObject方法返回bean实例的。
  • (2)InitializingBean:实现该接口会到bean初始化时调用afterPropertiesSet方法来执行自定义逻辑。

3.1.2、SqlSessionFactoryBean初始化(InitializingBean接口)

SqlSessionFactoryBean初始化即InitializingBean接口中定义的afterPropertiesSet方法,即SqlSessionFactoryBean实现了该方法,具体核心点集中在buildSqlSessionFactory(),具体逻辑如下:

public void afterPropertiesSet() throws Exception {//验证dataSource、sqlSessionFactoryBuilder和configuration是否已被赋值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");this.sqlSessionFactory = buildSqlSessionFactory();//创建sqlSessionFactory(核心)}

上述代码其实主要就是验证dataSource、sqlSessionFactoryBuilder和configuration是否已被赋值,紧接着在赋值的前提下进行sqlSessionFactory的初始化,接下来讲具体分析sqlSessionFactory如何被初始化的逻辑,如下:

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {final Configuration targetConfiguration;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) {//被清空时其他地方调用执行时第二次补救措施XMLConfigBuilder来创建ConfigurationxmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);targetConfiguration = xmlConfigBuilder.getConfiguration();} else {LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");targetConfiguration = new Configuration();Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);}Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);if (hasLength(this.typeAliasesPackage)) {//typeAliasesPackage包属性配置scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream().filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()).filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);//过滤得到指定包下的别名}if (!isEmpty(this.typeAliases)) {//typeAliases别名属性设置Stream.of(this.typeAliases).forEach(typeAlias -> {targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);//注册别名LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");});}if (!isEmpty(this.plugins)) {//插件属性pluginsStream.of(this.plugins).forEach(plugin -> {targetConfiguration.addInterceptor(plugin);LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");});}if (hasLength(this.typeHandlersPackage)) {//typeHandlersPackage包处理器类型scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())).filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null).forEach(targetConfiguration.getTypeHandlerRegistry()::register);//注册类型处理器}if (!isEmpty(this.typeHandlers)) {//typeHandlers类型处理器Stream.of(this.typeHandlers).forEach(typeHandler -> {targetConfiguration.getTypeHandlerRegistry().register(typeHandler);LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");});}if (!isEmpty(this.scriptingLanguageDrivers)) {//scriptingLanguageDrivers属性设置Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {targetConfiguration.getLanguageRegistry().register(languageDriver);LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");});}Optional.ofNullable(this.defaultScriptingLanguageDriver).ifPresent(targetConfiguration::setDefaultScriptingLanguage);if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmlstry {targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException e) {throw new NestedIOException("Failed getting a databaseId", e);}}Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);if (xmlConfigBuilder != null) {try {xmlConfigBuilder.parse();//configuration解析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();}}targetConfiguration.setEnvironment(new Environment(this.environment,this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,this.dataSource));if (this.mapperLocations != null) {//mapperLocations批量映射文件属性处理if (this.mapperLocations.length == 0) {LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");} else {for (Resource mapperLocation : this.mapperLocations) {//批量配置时通过循环处理映射文件if (mapperLocation == null) {continue;}try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());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.");}return this.sqlSessionFactoryBuilder.build(targetConfiguration);}

通过对sqlSessionFactory的初始化逻辑中可以看出,实际上就是对sqlSessionFactory配置里面的属性进行解析处理,在mybatis中的配置属性全部都可以使用spring来配置,只需要在配置sqlSessionFactory这个bean的时候将属性配置进去即可。
去掉mybatis配置,改由spring配置sqlSessionFactory时使用其属性配置如下:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="mapperLocations" value="com/spring/service" /><property name="cache" value="true"/>等等属性配置....
</bean>

3.1.3、获取SqlSessionFactoryBean实例(FactoryBean接口)

SqlSessionFactoryBean的实例,实际上是因为其实现了FactoryBean接口,所以当使用getBean时返回的是getObject方法产生的实例,具体如下:

public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();}return this.sqlSessionFactory;
}

3.2、MapperFactoryBean配置业务类型bean

单独使用mybatis获取连接映射:

SqlSession sqlSession = sqlSessionFactory.openSession(true);//创建SQLSession
StudentMybatisMapper mapper = sqlSession.getMapper(StudentMybatisMapper.class);//获取连接映射

集成在spring中使用mybatis获取连接映射:

//获取连接映射StudentMybatisMapper mapper = applicationContext.getBean("userMapper",StudentMybatisMapper.class);

从上述两处可知,

  1. 单独使用mybatis时,主要是通过sqlSession的getMapper来获取数据库接口连接映射,在这个过程中动态创建了代理类;
  2. 集成在spring中的mybatis时,主要使用applicationContext容器中的getBean方法获取bean,因StudentMybatisMapper是一个接口,也是使用了原生的mybatis的getMapper进行封装。
    所以无论单独使用mybatis,还是集成在spring中使用,归根结底都是在MapperFactorBean类中实现连接映射。

3.2.1、MapperFactoryBean层级图

MapperFactoryBean中也是使用两个重要的InitializingBean和FactoryBean接口类,其中核心的接口afterPropertiesSet和getObject,其主要接口作用在SqlSessionFactoryBean中已有介绍,此处不再赘述。

3.2.2、MapperFactoryBean初始化(InitializingBean接口)

MapperFactoryBean初始化流程图如下:

spring在初始化时会先调用InitializingBean类中的接口afterPropertiesSet来完成初始化逻辑,该初始化方法是由DaoSupport类来实现,经过源码跟踪如下,其主要逻辑是MapperFactoryBean子类实现checkDaoConfig方法和开放性设计initDao空实现,下面继续分析重点方法checkDaoConfig接口逻辑。

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {//MapperFactoryBean是DaoSupport的子类,由其实现初始化逻辑(模板设计模式)checkDaoConfig();try {//其方法内部是空实现,主要留给子类开放性设计(模板设计模式)initDao();} catch (Exception ex) {throw new BeanInitializationException("Initialization of DAO failed", ex);}}

从上面DaoSupport中的afterPropertiesSet可知,主要逻辑是checkDaoConfig中,但该方法是一个抽象类方法,是由子类来实现(模板设计模式),根据层级图可知就是MapperFactoryBean来实现的,所以初始化方法主要集中在MapperFactoryBean中

protected void checkDaoConfig() {//SqlSessionDaoSupport是MapperFactoryBean直属父类,主要是检查sqlSessionTemplate是否有值super.checkDaoConfig();//检查MapperFactoryBean配置的业务mapper的bean是否有配置mapperInterface属性即mapper接口notNull(this.mapperInterface, "Property 'mapperInterface' is required");Configuration configuration = getSqlSession().getConfiguration();//mybatis配置对象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();}}}

MapperFactoryBean中的checkDaoConfig方法中存在super.checkDaoConfig();其是调用直属父类SqlSessionDaoSupport中的checkDaoConfig方法,验证sqlSessionTemplate必须有值,用于定义连接映射的mapper接口,是要创建代理类,所以不能为空,否则将报错。

protected void checkDaoConfig() {//验证sqlSessionTemplate是否有值,该值是创建接口代理类,一定不能为空notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}

sqlSessionTemplate这个属性是SqlSessionTemplate类的实例,包含了下面几个重要属性:

public class SqlSessionTemplate implements SqlSession, DisposableBean {private final SqlSessionFactory sqlSessionFactory;//创建SqlSession会话的工厂类即spring中配置sqlSessionFactory属性或引用值private final ExecutorType executorType;//执行类型private final SqlSession sqlSessionProxy;//熟悉的sqlSession,获取接口映射private final PersistenceExceptionTranslator exceptionTranslator;//SqlSessionTemplate构造方法,注入属性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) {//引用sqlSessionFactory属性创建业务mapper的bean,必须强验证notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");notNull(executorType, "Property 'executorType' is required");this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;this.exceptionTranslator = exceptionTranslator;this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class }, new SqlSessionInterceptor());}
....省略其他方法
}

SqlSessionFactory类型的bean配置时会被赋值,在MapperFactoryBean配置业务bean时会引用sqlSessionFactory类型的bean,如Spring中的配置studentMapper Bean时引用的sqlSessionFactory Bean会触发上面SqlSessionTemplate构造方法。

<!--配置业务bean并在spring中映射mapper-->
<bean id="studentMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.spring.mapper.StudentMybatisMapper" /><property name="sqlSessionFactory" ref="sqlSessionFactory" /></bean>

3.2.3、MapperFactoryBean获取实例(FactroyBean接口)

MapperFactoryBean实现了FactoryBean接口,在spring中可以通过getBean方法获取该bean,实际上是从getObject方法中获得的实例,这段代码其实在单独使用mybatis时也有,spring只是对其进行封装,未做其他变化。

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

3.3、MapperScannerConfigurer批量配置映射器

单个映射器可以使用MapperFactoryBean去逐个配置,当遇到系统需要很多映射器的时候,不能傻瓜式的一个一个的配置,为了解决配置大量的映射器,MapperScannerConfigurer就是使用包属性来指定路径进行扫描进行批量映射器配置,如按照下面配置即对com.spring.mapper包下的所有mapper接口,多个包扫描时,使用逗号或分号进行隔开,被扫描到接口在spring中会采用默认的命名策略来命名。

  1. 如果没有注解,则采用非大写的非完全限定类名命名;
  2. 如果存在@Component或@Named注解,则使用其注解内的名称或默认bean的方式命名。
<!-- 批量配置映射器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.spring.mapper"/>
</bean>

3.3.1、MapperScannerConfigurer层级图

通过MapperScannerConfigurer层级图可以看出,比较重要的三个接口分别为:InitializingBean接口BeanFactoryPostProcessor接口BeanDefinitionRegistryPostProcessor接口

(1)InitializingBean接口

InitializingBean接口中的初始化属性afterPropertiesSet校验方法:委托给MapperScannerConfigurer类实现该方法,校验如下

 public void afterPropertiesSet() throws Exception {notNull(this.basePackage, "Property 'basePackage' is required");//仅仅校验了basePackage包属性必须存在}

(2)BeanFactoryPostProcessor接口

spring经典的后置处理器postProcessBeanFactory处理容器中的bean,此处是空实现,意义是开放性设计,给用户开一个自定义口子;

 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {//仅仅一个空实现,开放性设计}

(3)BeanDefinitionRegistryPostProcessor接口

MapperScannerConfigurer初始化流程图如下:

MapperScannerConfigurer在InitializingBean接口(afterPropertiesSet方法)和BeanFactoryPostProcessor接口 (postProcessBeanFactory方法)都没有发现到核心处理逻辑,剩下最后一个核心接口BeanDefinitionRegistryPostProcessor,其内果然有干活在,postProcessBeanDefinitionRegistry方法核心代码如下:

 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {//控制是否进行属性占位符替换即包属性值是否包含${}if (this.processPropertyPlaceHolders) {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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);if (StringUtils.hasText(lazyInitialization)) {scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));}//注册过滤器scanner.registerFilters();//扫描java文件scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}

3.3.2、processPropertyPlaceHolders属性

processPropertyPlaceHolders属性默认值为false,当配置basePackage属性值为占位符${}时,该值为true,主要进行属性占位符替换并创建一个内部工厂,仅包含扫描的映射器并对该工厂进行后置处理,核心逻辑和代码如下:

核心逻辑:

  1. 找到所有已经注册到spring容器中的PropertyResourceConfigurer类型的bean;
  2. 创建一个beanFactory,DefaultListableBeanFactory factory = new DefaultListableBeanFactory();来模拟spring容器(后置处理器完成时将清除)进行映射,MapperScannerConfigurer进行扫描bean注册到新环境中,通过PropertyPlaceholderConfigurer后置处理器调用完成后即可完成占位符的替换basePackage、sqlSessionFactory、sqlSessionTemplate和lazyInitialization等属性;

核心代码:

 private void processPropertyPlaceHolders() {//从容器中获取PropertyResourceConfigurer类型Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);//容器检查if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBeanDefinition(beanName);/*** PropertyResourceConfigurer 未显式执行属性占位符替换。 * 相反,创建一个仅包含此映射器扫描仪的 BeanFactory 并对工厂进行后置处理。*/DefaultListableBeanFactory factory = new DefaultListableBeanFactory();factory.registerBeanDefinition(beanName, mapperScannerBean);//将该批量扫描器注册到新创建的beanFactory//对每个PropertyResourceConfigurer类型的进行注册后置处理器postProcessBeanFactoryfor (PropertyResourceConfigurer prc : prcs.values()) {prc.postProcessBeanFactory(factory);//注册后置处理器}PropertyValues values = mapperScannerBean.getPropertyValues();//批量配置器的属性值this.basePackage = updatePropertyValue("basePackage", values);//更新属性占位符后的包属性值this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);//更新sqlSessionFactorythis.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);//更新sqlSessionTemplatethis.lazyInitialization = updatePropertyValue("lazyInitialization", values);//更新lazyInitialization}//对basePackage、sqlSessionFactory、sqlSessionTemplate、lazyInitialization空处理和环境设置this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders).orElse(null);}

3.3.3、根据配置生成过滤器

在postProcessBeanDefinitionRegistry方法中,很多属性的设置如addToConfig、annotationClass、markerInterface、sqlSessionFactory、sqlSessionTemplate等约定配置,通过配置来实现属性设值,但是对扫描结果造成影响的是scanner.registerFilters();这行代码——生成过滤器。

核心过滤器规则:

  • (1)annotationClass属性处理:使用AnnotationTypeFilter过滤器封装annotationClass属性,在扫描对应java文件时只处理标记有注解为annotationClass的mapper接口
  • (2)markerInterface属性处理:使用AssignableTypeFilter过滤器封装markerInterface属性,在扫描过程中只处理实现了markerInterface接口的mapper接口
  • (3)全局默认处理:当上述(1)、(2)两种其中一个存在配置时将会改变acceptAllInterfaces该值,将不执行全局默认扫描过滤配置,否则将使用默认扫描所有类的过滤策略;
  • (4)排除package-info结尾的文件。

核心过滤器代码:

  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) {@Overrideprotected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}//acceptAllInterfaces 基于上述两种过滤属性任一处理后都讲改变该值if (acceptAllInterfaces) {//全局默认过滤器,默认扫描所有类addIncludeFilter((metadataReader, metadataReaderFactory) -> true);}//排除package-info结尾的文件addExcludeFilter((metadataReader, metadataReaderFactory) -> {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");});}

3.3.4、扫描java文件

在经过文件占位符替换、各种属性设置、生成过滤器等操作后,剩下最后的一步扫描java文件,扫描工作由 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);中的scanner实例来扫描即scan方法具体实现。
scan方法核心逻辑:

  • (1)扫描java文件工作委托于doScan方法去完成;
  • (2)对includeAnnotationConfig属性处理,是否开启注解处理器,如ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor等。
public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();//扫描java文件委托给doScandoScan(basePackages);// includeAnnotationConfig属性设置,是否开启注解处理器if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);//注册注解处理器}return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}

spring风格模式,干活的永远是doXXX,正如扫描java文件,由ClassPathMapperScanner子类负责将扫描细节都封装到doScan里,并且部分逻辑封装到ClassPathMapperScanner类的父类ClassPathBeanDefinitionScanner(模板模式)中去执行,然后紧接着使用processBeanDefinitions方法去批量生成MapperFactoryBean类型的bean。

public Set<BeanDefinitionHolder> doScan(String... basePackages) {//调用父类ClassPathBeanDefinitionScanner类中的doScan,一些通用的被封装到上层类中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 {processBeanDefinitions(beanDefinitions);//生成Mapper映射的bean}return beanDefinitions;}
//批量生成MapperFactoryBean类型的beanprivate void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;//BeanDefinition的遍历处理即开始构造MapperFactoryBean类型的bean for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName+ "' mapperInterface");//mapper接口的处理即mapperInterface功能definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59definition.setBeanClass(this.mapperFactoryBeanClass);//addToConfig功能处理definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;//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;}//sqlSessionTemplateBeanName配置if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {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) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}// 自动注入模式,分为类型或名称,上述条件是否改变explicitFactoryUsed值来决定是否需要开启按类型注入if (!explicitFactoryUsed) {LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}definition.setLazyInit(lazyInitialization);}}

ClassPathBeanDefinitionScanner中的doScan负责解析多个包属性、设置findCandidateComponents内部方法生成的bean的scope属性、处理额外的注解如Lazy、Primary等,最后将生成符合的配置的BeanDefinition返回给ClassPathMapperScanner中的processBeanDefinitions处理生成MapperFactoryBean的bean即mapper映射接口。

   protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();//遍历扫描多个包的java路径for (String basePackage : basePackages) {/*** 扫描的核心关键点,根据包路径信息结合类文件路径拼接成绝对路径,生成ScannedGenericBeanDefinition类型的bean* 其中有使用到前面生成的过滤器规则,过滤部分文件路径*/Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//解析BeanDefinition并设置scopefor (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}//处理是否为AnnotatedBeanDefinition类型的bean,则需要处理注解Lazy、Primary、DependsOn、Role和Descriptionif (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//检测是否已经被注册过if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);//当前bean是否用于代理,如果是则需要进一步处理definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

ClassPathScanningCandidateComponentProvider类中的findCandidateComponents方法根据包路径信息结合类文件路径拼接成绝对路径,过滤部分文件路径isCandidateComponent方法,最终生成ScannedGenericBeanDefinition类型的bean。

public Set<BeanDefinition> findCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();try {//拼接文件的绝对路径String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + "/" + this.resourcePattern;Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);//应用文件过滤器规则,生成规则内的ScannedGenericBeanDefinition类型的beanif (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);} else {if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}} else {if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}} catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}} else {if (traceEnabled) {logger.trace("Ignored because not readable: " + resource);}}}} catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;
}

Spring源码分析八:Mybatis ORM映射框架原理相关推荐

  1. mybatis的使用及源码分析(八) mybatis的rowbounds分析

    Mybatis提供了一个简单的逻辑分页类RowBounds,其原理类似于在内存中做了一个分页,不是数据库层面的分页,性能不算好,谨慎使用 一. RowBounds源码分析 1 RowBounds源码: ...

  2. Spring源码分析——汇总全集

    文章目录 一.背景 二.源码分析目录 三.源码番外篇(补充) 更新时间 更新内容 备注 2022-04-01 Spring源码分析目录和计划 2022-04-10 Spring源码分析一:容器篇-re ...

  3. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  4. Spring 源码分析 (一)——迈向 Spring 之路

    一切都是从 Bean 开始的 在 1996 年,Java 还只是一个新兴的.初出茅庐的编程语言.人们之所以关注她仅仅是因为,可以使用 Java 的 Applet 来开发 Web 应用.但这些开发者很快 ...

  5. Spring 源码分析(四) ——MVC(二)概述

    随时随地技术实战干货,获取项目源码.学习资料,请关注源代码社区公众号(ydmsq666) from:Spring 源码分析(四) --MVC(二)概述 - 水门-kay的个人页面 - OSCHINA ...

  6. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  7. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  8. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  9. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

最新文章

  1. php页面刷新$_session会变化,每次刷新页面都会生产新的session_id
  2. Python字典部分源码分析,字典是无序的
  3. 随机森林c++_100天搞定机器学习|Day3334 随机森林
  4. android make 没反应
  5. pricing data of Service order is copied from BP master data
  6. 比微软kinect更强的视频跟踪算法--TLD跟踪算法介绍
  7. 在 Gitee 上使用 GPG公钥(Beta版)
  8. 阿里立秋:AI千人千面,淘宝如何做智能化UI测试?
  9. 设置图片和文字的垂直居中
  10. c10k java_c10k问题及其解决方案
  11. Steam 游戏服务器无法连接 steam 游戏无法启动 打开 microsoft store 错误代码 0x80131500
  12. 彩虹易支付最新版开源版源码分享
  13. 参考文献格式国家标准
  14. ios 点生成线路 百度地图_iOS百度地图开发之路径规划
  15. 车牌限行C语言雾霾指数,算法设计与程序实现判断车牌的单双号(背景知识)为了保障空气质量,减少雾霾,某市决定当空气污染严重时,实行汽车单双号限行,违反规定的车辆将予以处罚。如何让高清摄像-组卷网...
  16. SDUT—Python程序设计实验78(函数)
  17. 大数据获客,实时截流,真的有效果吗?
  18. 哈氏合金C276钢带、C22带钢、254SMO薄带精密箔带需求增速
  19. 解决精灵标注助手暂不支持导入pascal文件
  20. 学习C语言:自幂数最通俗讲解

热门文章

  1. 计算机游戏锁怎么打开,电脑玩游戏锁帧怎么办
  2. Linux chgrp 命令
  3. c语言延时变频1kHz和2kHz,1高频电子线路复习题及答案
  4. 【力扣刷题——字符串】附力扣链接、题目描述、解题方法及代码(344、541、剑指Offer05、151、剑指 Offer58、28、459)后续再补充
  5. 十倍效能提升——Web 基础研发体系的建立
  6. 服务器全息显示修改,柔性全息显示器:通过拉伸就能切换显示信息
  7. 恭喜 MOSN 社区成为可信开源社区共同体成员
  8. 解决sudo rosdep init和rosdep update各种疑难杂症
  9. 机器学习(一)-导论
  10. 我们这些普通的网民需要知道什么是 WEB2。0吗??【WEB2。0详解 预测模式】