当前位置:   article > 正文

mybatis-spring-boot-starter 原理分析

mybatis-spring-boot-starter

一、前言

 

mybatis-spring-boot-starter是什么?

 

mybatis-spring-boot-starter可以帮助你快速创建基于Spring Boot的MyBatis应用程序。

 

mybatis-spring-boot-starter可以达到什么效果?

 

● 构建独立的MyBatis应用程序
● 零模板
● 更少的XML配置文件

 

引入mybatis-spring-boot-starter模块之后,其可以:

● 自动检测DataSource
● 使用SqlSessionFactoryBean注册SqlSessionFactory 实例,并设置DataSource数据源
● 基于SqlSessionFactory自动注册SqlSessionTemplate实例
● 自动扫描@Mapper注解类,并通过SqlSessionTemplate注册到Spring Context中

mybatis-spring-boot-starter就是参照Spring Boot的设计思想,化繁为简,以简单注解的方式让用户快速上手。

 

下面我们简单的创建一个项目,让他跑起来:

首先,我们引入依赖:

  1. <dependency>
  2. <groupId>org.mybatis.spring.boot</groupId>
  3. <artifactId>mybatis-spring-boot-starter</artifactId>
  4. <version>2.1.0</version>
  5. </dependency>

配置数据源

  1. spring:
  2. datasource:
  3. url: jdbc:mysql://127.0.0.1:3306/auto?useUnicode=true&useSSL=false&characterEncoding=utf8
  4. username: root
  5. password: root
  6. driver-class-name: com.mysql.jdbc.Driver

创建Mapper

  1. @Repository
  2. public interface UserMapper {
  3. @Select("SELECT * FROM Person WHERE id = #{id}")
  4. Person selectUser(int id);
  5. @Delete("delete from person where id = #{id}")
  6. void deleteByPrimaryKey(int id);
  7. }

启动类:

  1. @MapperScan("com.fendo.auto.mapper") //扫描的mapper
  2. @SpringBootApplication
  3. public class AutoApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(AutoApplication.class, args);
  6. }
  7. }

这样就完成了一个简单的集成

 

二、原理分析

 

mybatis-spring-boot-starter的作用就是,在SpringBoot启动时,去扫描所有Mapper接口,然后为其增加一个代理实现类,在调用的过程中,我们实际调用的是这个代理对象。用过springboot的同学都是知道,springboot配置简单,更提供了大量的starter方便集成各个组件。每个组件基本只要依赖了jar包,基本不需要或者需要少量的配置信息就可以直接使用了,那么在这简单的starter背后的原理是什么呢?

 

统一的命名规范 XXX-spring-boot-starter (非必须)


你如果去看每个starter的的名字,比如mybatis的starter名字如下:

  1. <dependency>
  2. <groupId>org.mybatis.spring.boot</groupId>
  3. <artifactId>mybatis-spring-boot-starter</artifactId>
  4. <version>2.1.0</version>
  5. </dependency>

你会发现每个starter的artifactId都是以spring-boot-starter结尾,这个是普遍认同的一个规范,不是必须按照这个名字来,也可以自定义名字,原因下面介绍。

统一的配置文件 spring.factories


必须在classpath下的META-INF文件夹下创建一个spring.factories,本质是一个Properties,所以得按照key-value的形式进行配置。自动配置的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration。我们看下mybatis的配置:

  1. # Auto Configure
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  3. org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
  4. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

我们看到,这个文件配置了一个key:value格式的数据

1)key是:org.springframework.boot.autoconfigure.EnableAutoConfiguration

2)value是:org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

key声明的是一个接口,value则是这个接口对应的实现类,如果有多个则以","符号分割。

简单来说,spring.factories就像是工厂一样配置了大量的接口对应的实现类,我们通过这些配置 + 反射处理就可以拿到相应的实现类。这种类似于插件式的设计方式,只要引入对应的jar包,那么对应的spring.factories就会被扫描到,对应的实现类也就会被实例化,如果不需要的时候,直接把jar包移除即可。

 

注解说明:

  1. @Configuration
  2. Configuration 是springboot提供的,表示这是一个配置类,其实它的作用跟@Bean是一样的,都是让spring实例化该类。
  3. @ConditionalOnClass
  4. 表示存在某些类的时候,才进行初始化。
  5. @AutoConfigureBefore
  6. 表示必须先初始化某个类
  7. @AutoConfigureAfter
  8. 表示初始化完成之后加载某个类
  9. @ConditionalOnBean
  10. 只有在上下文中存在某个类才运行
  11. @EnableConfigurationProperties
  12. ConfigurationProperties注解主要用来把properties配置文件转化为bean来使用的
  13. @EnableConfigurationProperties注解的作用是@ConfigurationProperties注解生效。
  14. 如果只配置@ConfigurationProperties注解,在IOC容器中是获取不到properties配置文件转化的bean的。
  15. @ConditionalOnMissingBean
  16. 作用在@bean定义上,它的作用就是在容器加载它作用的bean时
  17. 检查容器中是否存在目标类型(ConditionalOnMissingBean注解的value值)的bean了
  18. 如果存在这跳过原始bean的BeanDefinition加载动作。
  19. 简单理解就是@ConditionalOnBean是依赖,@ConditionalOnMissBean是排斥,@Conditional为条件
  20. @ConditionalOnSingleCandidate 当给定类型的bean存在并且指定为Primary的给定类型存在时,返回true
  21. @ConditionalOnMissingBean 当给定的类型、类名、注解、昵称在beanFactory中不存在时返回true.各类型间是or的关系
  22. @ConditionalOnBean 与上面相反,要求bean存在
  23. @ConditionalOnMissingClass 当给定的类名在类路径上不存在时返回true,各类型间是and的关系
  24. @ConditionalOnClass 与上面相反,要求类存在
  25. @ConditionalOnCloudPlatform 当所配置的CloudPlatform为激活时返回true
  26. @ConditionalOnExpression spel表达式执行为true
  27. @ConditionalOnJava 运行时的java版本号是否包含给定的版本号.如果包含,返回匹配,否则,返回不匹配
  28. @ConditionalOnProperty 要求配置属性匹配条件
  29. @ConditionalOnJndi 给定的jndi的Location 必须存在一个.否则,返回不匹配
  30. @ConditionalOnNotWebApplication web环境不存在时
  31. @ConditionalOnWebApplication web环境存在时
  32. @ConditionalOnResource 要求制定的资源存在

mybatis-spring-boot-starter中最主要的一个类就是MybatisAutoConfiguration,MybatisAutoConfiguration 是spring boot 下 mybatis 默认的配置类,只要开启了注释了 @EnableAutoConfiguration 就可以了,spring boot 会默认执行。在SpringBoot启动的过程中 @SpringBootApplication 中组合了 EnableAutoConfiguration ,属于spring boot 自动配置和启动过程,SpringBoot启动时会进入到MybatisAutoConfiguration这个类里,这是一个自动配置类,这里面初始化了SqlSessionFactory、SqlSessionTemplate等一些我们在Spring项目中需要手动配置的,源码如下该类如下:

  1. //Configuration注解:说明这是spring的配置项,容器初始化的时候要进行解析处理
  2. @org.springframework.context.annotation.Configuration
  3. //ConditionalOnClass注解:有类SqlSessionFactory(Mybatis),SqlSessionFactoryBean(Mybatis-Spring)的时候才进行解析处理
  4. @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
  5. //ConditionalOnBean注解:容器中有DataSource Bean的时候才进行解析处理
  6. @ConditionalOnSingleCandidate(DataSource.class)
  7. //ConfigurationProperties注解主要用来把properties配置文件转化为bean来使用的
  8. //而EnableConfigurationProperties注解的作用是ConfigurationProperties注解生效。
  9. //如果只配置@ConfigurationProperties注解,在IOC容器中是获取不到properties配置文件转化的bean的。
  10. //MybatisProperties是mybatis的配置类,将properties中的配置文件转换成MybatisProperties对象
  11. //MybatisProperties上有@ConfigurationProperties(prefix = "mybatis")注解。
  12. //EnableConfigurationProperties注解和MybatisProperties类:配置自己的Mybatis 属性,在Application.Properties中。
  13. @EnableConfigurationProperties(MybatisProperties.class)
  14. //AutoConfigureAfter注解: 在DataSourceAutoConfiguration,MybatisLanguageDriverAutoConfiguration后解析
  15. @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
  16. public class MybatisAutoConfiguration implements InitializingBean {
  17. private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
  18. private final MybatisProperties properties;
  19. private final Interceptor[] interceptors;
  20. private final TypeHandler[] typeHandlers;
  21. private final LanguageDriver[] languageDrivers;
  22. private final ResourceLoader resourceLoader;
  23. private final DatabaseIdProvider databaseIdProvider;
  24. private final List<ConfigurationCustomizer> configurationCustomizers;
  25. /**
  26. * 构造方法
  27. * @param properties
  28. * @param interceptorsProvider
  29. * @param typeHandlersProvider
  30. * @param languageDriversProvider
  31. * @param resourceLoader
  32. * @param databaseIdProvider
  33. * @param configurationCustomizersProvider
  34. */
  35. public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
  36. ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
  37. ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
  38. ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
  39. this.properties = properties;
  40. this.interceptors = interceptorsProvider.getIfAvailable();
  41. this.typeHandlers = typeHandlersProvider.getIfAvailable();
  42. this.languageDrivers = languageDriversProvider.getIfAvailable();
  43. this.resourceLoader = resourceLoader;
  44. this.databaseIdProvider = databaseIdProvider.getIfAvailable();
  45. this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  46. }
  47. @Override
  48. public void afterPropertiesSet() {
  49. checkConfigFileExists();
  50. }
  51. private void checkConfigFileExists() {
  52. if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
  53. Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
  54. Assert.state(resource.exists(),
  55. "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
  56. }
  57. }
  58. /**
  59. * 创建SqlSessionFactory
  60. * @param dataSource
  61. * @return
  62. * @throws Exception
  63. */
  64. @Bean
  65. //@ConditionalOnMissingBean注解判断是否执行初始化代码,即如果用户已经创建了bean,则相关的初始化代码不再执行。
  66. @ConditionalOnMissingBean
  67. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  68. SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  69. factory.setDataSource(dataSource);
  70. factory.setVfs(SpringBootVFS.class);
  71. if (StringUtils.hasText(this.properties.getConfigLocation())) {
  72. factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  73. }
  74. //应用配置
  75. applyConfiguration(factory);
  76. if (this.properties.getConfigurationProperties() != null) {
  77. factory.setConfigurationProperties(this.properties.getConfigurationProperties());
  78. }
  79. if (!ObjectUtils.isEmpty(this.interceptors)) {
  80. factory.setPlugins(this.interceptors);
  81. }
  82. if (this.databaseIdProvider != null) {
  83. factory.setDatabaseIdProvider(this.databaseIdProvider);
  84. }
  85. if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
  86. factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
  87. }
  88. if (this.properties.getTypeAliasesSuperType() != null) {
  89. factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
  90. }
  91. if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
  92. factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
  93. }
  94. if (!ObjectUtils.isEmpty(this.typeHandlers)) {
  95. factory.setTypeHandlers(this.typeHandlers);
  96. }
  97. if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
  98. factory.setMapperLocations(this.properties.resolveMapperLocations());
  99. }
  100. Set<String> factoryPropertyNames = Stream
  101. .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
  102. .collect(Collectors.toSet());
  103. Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
  104. if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
  105. // Need to mybatis-spring 2.0.2+
  106. factory.setScriptingLanguageDrivers(this.languageDrivers);
  107. if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
  108. defaultLanguageDriver = this.languageDrivers[0].getClass();
  109. }
  110. }
  111. if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
  112. // Need to mybatis-spring 2.0.2+
  113. factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
  114. }
  115. return factory.getObject();
  116. }
  117. /**
  118. * 应用配置
  119. * @param factory
  120. */
  121. private void applyConfiguration(SqlSessionFactoryBean factory) {
  122. Configuration configuration = this.properties.getConfiguration();
  123. if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
  124. configuration = new Configuration();
  125. }
  126. if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
  127. //ConfigurationCustomizer 属于 mybatis-spring包中的接口
  128. //ConfigurationCustomizer 主要是提供一个接口可以获取 mybatis 的 configuration 或者更改 configuration 中的属性,比如向configuration 中注册插件
  129. for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
  130. customizer.customize(configuration);
  131. }
  132. }
  133. factory.setConfiguration(configuration);
  134. }
  135. /**
  136. * 创建sqlSessionTemplate
  137. * @param sqlSessionFactory
  138. * @return
  139. */
  140. @Bean
  141. @ConditionalOnMissingBean
  142. public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  143. ExecutorType executorType = this.properties.getExecutorType();
  144. if (executorType != null) {
  145. return new SqlSessionTemplate(sqlSessionFactory, executorType);
  146. } else {
  147. return new SqlSessionTemplate(sqlSessionFactory);
  148. }
  149. }
  150. /**
  151. * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
  152. * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
  153. * similar to using Spring Data JPA repositories.
  154. */
  155. public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
  156. private BeanFactory beanFactory;
  157. @Override
  158. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  159. if (!AutoConfigurationPackages.has(this.beanFactory)) {
  160. logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
  161. return;
  162. }
  163. logger.debug("Searching for mappers annotated with @Mapper");
  164. List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
  165. if (logger.isDebugEnabled()) {
  166. packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
  167. }
  168. //处理@Mapper标记的接口
  169. BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  170. builder.addPropertyValue("processPropertyPlaceHolders", true);
  171. builder.addPropertyValue("annotationClass", Mapper.class);
  172. builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
  173. BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
  174. Stream.of(beanWrapper.getPropertyDescriptors())
  175. // Need to mybatis-spring 2.0.2+
  176. .filter(x -> x.getName().equals("lazyInitialization")).findAny()
  177. .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
  178. registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
  179. }
  180. @Override
  181. public void setBeanFactory(BeanFactory beanFactory) {
  182. this.beanFactory = beanFactory;
  183. }
  184. }
  185. /**
  186. * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
  187. * mappers based on the same component-scanning path as Spring Boot itself.
  188. */
  189. @org.springframework.context.annotation.Configuration
  190. //把用到的资源导入到当前容器中
  191. //@Import注解把AutoConfiguredMapperScannerRegistrar的bean注入到当前容器中。
  192. @Import(AutoConfiguredMapperScannerRegistrar.class)
  193. //如果在容器中没有 MapperFactoryBean ,MapperScannerConfigurer就导入AutoConfiguredMapperScannerRegistrar 配置类。
  194. @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
  195. public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
  196. @Override
  197. public void afterPropertiesSet() {
  198. logger.debug(
  199. "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
  200. }
  201. }
  202. }

所用的注解就是上面所说的:

  1. Configuration注解:说明这是spring的配置项,容器初始化的时候要进行解析处理
  2. ConditionalOnClass注解:有类SqlSessionFactory(Mybatis),SqlSessionFactoryBean(Mybatis-Spring)的时候才进行解析处理
  3. ConditionalOnBean:容器中有DataSource Bean的时候才进行解析处理
  4. AutoConfigureAfter注解: 在DataSourceAutoConfiguration后解析
  5. EnableConfigurationProperties注解和MybatisProperties类:配置自己的Mybatis 属性,在Application.Properties中。

其中有个MapperScannerRegistrarNotFoundConfiguration静态方法:

  1. @org.springframework.context.annotation.Configuration
  2. //把用到的资源导入到当前容器中
  3. //@Import注解把AutoConfiguredMapperScannerRegistrar的bean注入到当前容器中。
  4. @Import(AutoConfiguredMapperScannerRegistrar.class)
  5. //如果在容器中没有 MapperFactoryBean ,MapperScannerConfigurer就导入AutoConfiguredMapperScannerRegistrar 配置类。
  6. @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
  7. public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
  8. @Override
  9. public void afterPropertiesSet() {
  10. logger.debug(
  11. "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
  12. }
  13. }

这个类实现了Spring的注册bean的接口,并注册了一个MapperScannerConfigurer,如果在启动类上面没有@MapperScan就会执行AutoConfiguredMapperScannerRegistrar查找扫描出标注有 @Mapper 注解类的接口,并注册到Spring容器中。

 

@Mapper与@MapperScan

 

在项目中大多数用的都是@MapperScan注解,指定basePackages,扫描mybatis Mapper接口类

另外一种方式是用@Mapper注解,其实这两种方法扫描配置用的是一个地方,只是扫描入口不同。从mybatis3.4.0开始加入了@Mapper注解,目的就是为了不再写mapper映射文件(xxxxMapper.xml),用法也很简单:

  1. **
  2. * 添加了@Mapper注解之后这个接口在编译时会生成相应的实现类
  3. *
  4. * 需要注意的是:这个接口中不可以定义同名的方法,因为会生成相同的id
  5. * 也就是说这个接口是不支持重载的
  6. */
  7. @Mapper
  8. public interface User {
  9. @Select("select * from user where name = #{name}")
  10. public User find(String name);
  11. @Select("select * from user where name = #{name} and pwd = #{pwd}")
  12. /**
  13. * 对于多个参数来说,每个参数之前都要加上@Param注解,
  14. * 要不然会找不到对应的参数进而报错
  15. */
  16. public User login(@Param("name")String name, @Param("pwd")String pwd);
  17. }

这种方式需要每个接口上面都加上这个注解,很麻烦,所以就出现了@MapperScan,@MapperScan只需要指定包名就行了,会自动扫描。

@MapperScan是根据其注解上MapperScannerRegistrar进行自动配置的,最终调用的自动配置代码和下面的代码一致,@Mapper自动配置的程序入口是 MybatisAutoConfiguration类的最下面,位置在mybatis-spring-boot-starter包下面的mybatis-spring-boot-autoconfigure

上面代码的逻辑是 如果标注了@MapperScan 的注解,将会生成 MapperFactoryBean, 如果没有标注@MapperScan 也就是没有MapperFactoryBean的实例,就走@Import里面的配置,下面看看AutoConfiguredMapperScannerRegistrar的配置,它是MybatisAutoConfiguration类下的内部类,MapperScannerConfigurer类实现了spring的注册bean的接口,最主要的方法就是postProcessBeanDefinitionRegistry

  1. @Override
  2. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  3. if (this.processPropertyPlaceHolders) {
  4. processPropertyPlaceHolders();
  5. }
  6. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  7. scanner.setAddToConfig(this.addToConfig);
  8. scanner.setAnnotationClass(this.annotationClass);
  9. scanner.setMarkerInterface(this.markerInterface);
  10. scanner.setSqlSessionFactory(this.sqlSessionFactory);
  11. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  12. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  13. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  14. scanner.setResourceLoader(this.applicationContext);
  15. scanner.setBeanNameGenerator(this.nameGenerator);
  16. scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  17. if (StringUtils.hasText(lazyInitialization)) {
  18. scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  19. }
  20. scanner.registerFilters();
  21. scanner.scan(
  22. StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  23. }

这个方法中实例化了一个ClassPathMapperScanner,并调用了scan方法,点击进去之后

  1. @Override
  2. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  3. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  4. if (beanDefinitions.isEmpty()) {
  5. LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
  6. + "' package. Please check your configuration.");
  7. } else {
  8. processBeanDefinitions(beanDefinitions);
  9. }
  10. return beanDefinitions;
  11. }
  12. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  13. GenericBeanDefinition definition;
  14. for (BeanDefinitionHolder holder : beanDefinitions) {
  15. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  16. String beanClassName = definition.getBeanClassName();
  17. LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
  18. + "' mapperInterface");
  19. // the mapper interface is the original class of the bean
  20. // but, the actual class of the bean is MapperFactoryBean
  21. definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
  22. definition.setBeanClass(this.mapperFactoryBeanClass);
  23. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  24. boolean explicitFactoryUsed = false;
  25. if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
  26. definition.getPropertyValues().add("sqlSessionFactory",
  27. new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
  28. explicitFactoryUsed = true;
  29. } else if (this.sqlSessionFactory != null) {
  30. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  31. explicitFactoryUsed = true;
  32. }
  33. if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
  34. if (explicitFactoryUsed) {
  35. LOGGER.warn(
  36. () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  37. }
  38. definition.getPropertyValues().add("sqlSessionTemplate",
  39. new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
  40. explicitFactoryUsed = true;
  41. } else if (this.sqlSessionTemplate != null) {
  42. if (explicitFactoryUsed) {
  43. LOGGER.warn(
  44. () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  45. }
  46. definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
  47. explicitFactoryUsed = true;
  48. }
  49. if (!explicitFactoryUsed) {
  50. LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
  51. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  52. }
  53. definition.setLazyInit(lazyInitialization);
  54. }
  55. }

这个就是spring boot注册mapper的核心方法了,在上面的方法中可以发现通过definition.setBeanClass(this.mapperFactoryBeanClass)为我们定义的mapper添加了一个代理对象MapperFactoryBean,前往MapperFactoryBean可以发现这个类实现了Spring提供的FactoryBean接口,里面有个核心方法

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

这个方法是放回一个bean对象,继续进入getMapper()方法

该方法有三个实现类,选择DefaultSqlSession

  1. @Override
  2. public <T> T getMapper(Class<T> type) {
  3. return configuration.getMapper(type, this);
  4. }

调用的是Configuration中的

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. return mapperRegistry.getMapper(type, sqlSession);
  3. }

找到MapperRegister类,里面有一个核心方法

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
  3. if (mapperProxyFactory == null) {
  4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5. } else {
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession);
  8. } catch (Exception var5) {
  9. throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
  10. }
  11. }
  12. }

这个方法通过调用MapperProxyFactory类,生成一个代理实例MapperProxy并返回,MapperProxyFactory类源码如下

  1. public class MapperProxyFactory<T> {
  2. private final Class<T> mapperInterface;
  3. private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  4. public MapperProxyFactory(Class<T> mapperInterface) {
  5. this.mapperInterface = mapperInterface;
  6. }
  7. public Class<T> getMapperInterface() {
  8. return mapperInterface;
  9. }
  10. public Map<Method, MapperMethod> getMethodCache() {
  11. return methodCache;
  12. }
  13. @SuppressWarnings("unchecked")
  14. protected T newInstance(MapperProxy<T> mapperProxy) {
  15. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  16. }
  17. public T newInstance(SqlSession sqlSession) {
  18. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  19. return newInstance(mapperProxy);
  20. }
  21. }

到这里,SpringBoot初始化Mapper完毕,接下来在我们调用自己定义的Mapper的时候,实际上调用的便是MapperProxy这个代理对象了,MapperProxy类实现了InvocationHandler接口,这个类中最核心的方法便是Invoke方法

  1. public class MapperProxy<T> implements InvocationHandler, Serializable {
  2. private static final long serialVersionUID = -6424540398559729838L;
  3. private final SqlSession sqlSession;
  4. private final Class<T> mapperInterface;
  5. private final Map<Method, MapperMethod> methodCache;
  6. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  7. this.sqlSession = sqlSession;
  8. this.mapperInterface = mapperInterface;
  9. this.methodCache = methodCache;
  10. }
  11. @Override
  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  13. try {
  14. if (Object.class.equals(method.getDeclaringClass())) {
  15. return method.invoke(this, args);
  16. } else if (method.isDefault()) {
  17. return invokeDefaultMethod(proxy, method, args);
  18. }
  19. } catch (Throwable t) {
  20. throw ExceptionUtil.unwrapThrowable(t);
  21. }
  22. final MapperMethod mapperMethod = cachedMapperMethod(method);
  23. return mapperMethod.execute(sqlSession, args);
  24. }
  25. private MapperMethod cachedMapperMethod(Method method) {
  26. return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  27. }
  28. private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
  29. throws Throwable {
  30. final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
  31. .getDeclaredConstructor(Class.class, int.class);
  32. if (!constructor.isAccessible()) {
  33. constructor.setAccessible(true);
  34. }
  35. final Class<?> declaringClass = method.getDeclaringClass();
  36. return constructor
  37. .newInstance(declaringClass,
  38. MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
  39. | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
  40. .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  41. }
  42. }

通过mapperMethod.execute()方法去执行mybatis自己封装的操作数据库的操作了,execute()源码如下:

  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2. Object result;
  3. switch (command.getType()) {
  4. case INSERT: {
  5. Object param = method.convertArgsToSqlCommandParam(args);
  6. result = rowCountResult(sqlSession.insert(command.getName(), param));
  7. break;
  8. }
  9. case UPDATE: {
  10. Object param = method.convertArgsToSqlCommandParam(args);
  11. result = rowCountResult(sqlSession.update(command.getName(), param));
  12. break;
  13. }
  14. case DELETE: {
  15. Object param = method.convertArgsToSqlCommandParam(args);
  16. result = rowCountResult(sqlSession.delete(command.getName(), param));
  17. break;
  18. }
  19. case SELECT:
  20. if (method.returnsVoid() && method.hasResultHandler()) {
  21. executeWithResultHandler(sqlSession, args);
  22. result = null;
  23. } else if (method.returnsMany()) {
  24. result = executeForMany(sqlSession, args);
  25. } else if (method.returnsMap()) {
  26. result = executeForMap(sqlSession, args);
  27. } else if (method.returnsCursor()) {
  28. result = executeForCursor(sqlSession, args);
  29. } else {
  30. Object param = method.convertArgsToSqlCommandParam(args);
  31. result = sqlSession.selectOne(command.getName(), param);
  32. if (method.returnsOptional()
  33. && (result == null || !method.getReturnType().equals(result.getClass()))) {
  34. result = Optional.ofNullable(result);
  35. }
  36. }
  37. break;
  38. case FLUSH:
  39. result = sqlSession.flushStatements();
  40. break;
  41. default:
  42. throw new BindingException("Unknown execution method for: " + command.getName());
  43. }
  44. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  45. throw new BindingException("Mapper method '" + command.getName()
  46. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  47. }
  48. return result;
  49. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/498157
推荐阅读
相关标签
  

闽ICP备14008679号