小言_互联网的博客

Mybatis学习笔记(一)- Mapper整合和注入原理分析流程

360人阅读  评论(0)

上期中我们主要学习了Spring的动态bean注册,其中的主要接口是ImportBeanDefinitionRegistrar,在文中我们还主要学习接口的上游做了哪些事情。今天我们主要通过mybatis的mapper管理来学习一下该接口的下游方法调用过程。据此也尝试搞清楚mybatis的mapper的管理过程。

对此我们就从registerBeanDefinitions方法看起。


   
  1. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  2. //拿到具体的注解并转化成成annotationAttributes
  3. AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  4. //实例化一个自定义的扫描器
  5. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  6. if ( this.resourceLoader != null) {
  7. scanner.setResourceLoader( this.resourceLoader);
  8. }
  9. //获取注解元素annotationClass
  10. Class extends Annotation> annotationClass = annoAttrs.getClass( "annotationClass");
  11. if (!Annotation.class. equals(annotationClass)) {
  12. scanner.setAnnotationClass(annotationClass);
  13. }
  14. //获取要被扫描过程检测的接口,由markerinterface元素指定。
  15. Class markerInterface = annoAttrs.getClass("markerInterface");
  16. if (!Class.class. equals(markerInterface)) {
  17. scanner.setMarkerInterface(markerInterface);
  18. }
  19. //设置类名生成器
  20. Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  21. if (!BeanNameGenerator.class. equals(generatorClass)) {
  22. scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
  23. }
  24. //设置mapper管理器,会将扫描的mapper类放到核心配置类configuration中
  25. Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  26. if (!MapperFactoryBean.class. equals(mapperFactoryBeanClass)) {
  27. scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
  28. }
  29. //设置sqlsessionTemplate的名称
  30. scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
  31. //设置sqlsessionfactory的名称
  32. scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
  33. List basePackages = new ArrayList();
  34. //获取要扫描的包名称
  35. String[] var10 = annoAttrs.getStringArray( "value");
  36. int var11 = var10.length;
  37. int var12;
  38. String pkg;
  39. for(var12 = 0; var12 < var11; ++var12) {
  40. pkg = var10[var12];
  41. if (StringUtils.hasText(pkg)) {
  42. basePackages. add(pkg);
  43. }
  44. }
  45. //从beasePackages中获取要扫描的包名
  46. var10 = annoAttrs.getStringArray( "basePackages");
  47. var11 = var10.length;
  48. for(var12 = 0; var12 < var11; ++var12) {
  49. pkg = var10[var12];
  50. if (StringUtils.hasText(pkg)) {
  51. basePackages. add(pkg);
  52. }
  53. }
  54. //从basePackageClasses中获取要扫描的类的全路径,然后获取要扫描的包名
  55. Class[] var15 = annoAttrs.getClassArray( "basePackageClasses");
  56. var11 = var15.length;
  57. for(var12 = 0; var12 < var11; ++var12) {
  58. Class clazz = var15[var12];
  59. basePackages. add(ClassUtils.getPackageName(clazz));
  60.         }
  61. String mapperHelperRef = annoAttrs.getString( "mapperHelperRef");
  62. String[] properties = annoAttrs.getStringArray( "properties");
  63. if (StringUtils.hasText(mapperHelperRef)) {
  64. scanner.setMapperHelperBeanName(mapperHelperRef);
  65. } else if (properties != null && properties.length > 0) {
  66. scanner.setMapperProperties(properties);
  67. } else {
  68. try {
  69. scanner.setMapperProperties( this.environment);
  70. } catch (Exception var14) {
  71. LOGGER.warn( "只有 Spring Boot 环境中可以通过 Environment(配置文件,环境变量,运行参数等方式) 配置通用 Mapper,其他环境请通过 @MapperScan 注解中的 mapperHelperRef 或 properties 参数进行配置!如果你使用 tk.mybatis.mapper.session.Configuration 配置的通用 Mapper,你可以忽略该错误!", var14);
  72. }
  73. }
  74. //设置可以被扫描到的标志
  75. scanner.registerFilters();
  76. //进行for循环遍历
  77. scanner.doScan(StringUtils.toStringArray(basePackages));
  78. }

在registerfilters方法中,就是通过设置需要被扫描的类的一些标记信息。要么是注解,要么是接口。


   
  1. public void registerFilters() {
  2. boolean acceptAllInterfaces = true;
  3. if ( this.annotationClass != null) {
  4. //设置需要被扫描的注解,通过上面的注解上的元素设定
  5. this.addIncludeFilter( new AnnotationTypeFilter( this.annotationClass));
  6. acceptAllInterfaces = false;
  7. }
  8. //设置需要被拦截的接口
  9. if ( this.markerInterface != null) {
  10. this.addIncludeFilter( new AssignableTypeFilter( this.markerInterface) {
  11. protected boolean matchClassName(String className) {
  12. return false;
  13. }
  14. });
  15. acceptAllInterfaces = false;
  16. }
  17. //是否拦截所有的接口
  18. if (acceptAllInterfaces) {
  19. this.addIncludeFilter( new TypeFilter() {
  20. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
  21. return true;
  22. }
  23. });
  24. }
  25. //不需要被拦截的类型
  26. this.addExcludeFilter( new TypeFilter() {
  27. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
  28. String className = metadataReader.getClassMetadata().getClassName();
  29. return className.endsWith( "package-info") ? true : metadataReader.getAnnotationMetadata().hasAnnotation( "tk.mybatis.mapper.annotation.RegisterMapper");
  30. }
  31. });
  32.     }

通过上述两个步骤,我们知道mybatis先是通过@mapperscan注解设置一些值,然后通过设置需要被拦截的类的一些基本信息。然后就要开始for循环逐个扫描包路径了。


   
  1. public SetdoScan(String... basePackages) {
  2. //直接进行扫描,将符合条件的类beandefinition信息进行返回
  3. Set beanDefinitions = super.doScan(basePackages);
  4. if (beanDefinitions.isEmpty()) {
  5. this.logger.warn( "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  6. } else {
  7. //对符合条件的bean进行一些填充
  8. this.processBeanDefinitions(beanDefinitions);
  9. }
  10. return beanDefinitions;
  11. }

寻找符合条件的bean。


   
  1. public SetfindCandidateComponents(String basePackage) {
  2. if ( this.componentsIndex != null && indexSupportsIncludeFilters()) {
  3. return addCandidateComponentsFromIndex( this.componentsIndex, basePackage);
  4. }
  5. else {
  6. return scanCandidateComponents(basePackage);
  7. }
  8. }

   
  1. //根据设置的拦截信息进行判断
  2. private boolean indexSupportsIncludeFilters() {
  3. for (TypeFilter includeFilter : this.includeFilters) {
  4. if (!indexSupportsIncludeFilter(includeFilter)) {
  5. return false;
  6. }
  7. }
  8. return true;
  9. }
  10. private boolean indexSupportsIncludeFilter(TypeFilter filter) {
  11. if (filter instanceof AnnotationTypeFilter) {
  12. Class annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
  13. return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed. class, annotation) ||
  14. annotation.getName().startsWith( "javax."));
  15. }
  16. if (filter instanceof AssignableTypeFilter) {
  17. Class target = ((AssignableTypeFilter) filter).getTargetType();
  18. return AnnotationUtils.isAnnotationDeclaredLocally(Indexed. class, target);
  19. }
  20. return false;
  21. }
  22. //这块判断是接口哦,他可以扫描接口
  23. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  24. return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  25. }

通过上述对bean是否符合条件进行判断之后,就开始对扫描到的bean,也就是我们的mapper进行处理了。如下所示:


   
  1. private void processBeanDefinitions(SetbeanDefinitions) {
  2. Iterator var3 = beanDefinitions.iterator();
  3. while(var3.hasNext()) {
  4. //拿到具体的mapper
  5. BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
  6. GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
  7. if ( this.logger.isDebugEnabled()) {
  8. this.logger.debug( "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
  9. }
  10. //设置构造函数
  11. definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
  12. //设置该bean的name,mapperfactoryBean。这里设置主要是为了注入到mapperfacatorybean中
  13. definition.setBeanClass( this.mapperFactoryBean.getClass());
  14. if (StringUtils.hasText( this.mapperHelperBeanName)) {
  15. definition.getPropertyValues().add( "mapperHelper", new RuntimeBeanReference( this.mapperHelperBeanName));
  16. } else {
  17. if ( this.mapperHelper == null) {
  18. this.mapperHelper = new MapperHelper();
  19. }
  20. definition.getPropertyValues().add( "mapperHelper", this.mapperHelper);
  21. }
  22. //设置addToConfig属性
  23. definition.getPropertyValues().add( "addToConfig", this.addToConfig);
  24. boolean explicitFactoryUsed = false;
  25. if (StringUtils.hasText( this.sqlSessionFactoryBeanName)) {
  26. //sqlSessionFactory,设置sqlSessionFactory类
  27. definition.getPropertyValues().add( "sqlSessionFactory", 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. //设置sqlSessionTemplate类名称
  34. if (StringUtils.hasText( this.sqlSessionTemplateBeanName)) {
  35. if (explicitFactoryUsed) {
  36. this.logger.warn( "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  37. }
  38. definition.getPropertyValues().add( "sqlSessionTemplate", new RuntimeBeanReference( this.sqlSessionTemplateBeanName));
  39. explicitFactoryUsed = true;
  40. } else if ( this.sqlSessionTemplate != null) {
  41. if (explicitFactoryUsed) {
  42. this.logger.warn( "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  43. }
  44. definition.getPropertyValues().add( "sqlSessionTemplate", this.sqlSessionTemplate);
  45. explicitFactoryUsed = true;
  46. }
  47. if (!explicitFactoryUsed) {
  48. if ( this.logger.isDebugEnabled()) {
  49. this.logger.debug( "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
  50. }
  51. definition.setAutowireMode( 2);
  52. }
  53. }

我们通过查看MapperFactoryBean的构造函数发现如下:

通过上述操作,我们知道我们的的mapper已经进入beandefinition中了,也就是mybatis需要的哪些接口。通过上述的一些分析。接口扫描和注册基本已经完成。现在就成了spring的整合问题。为此有了mybatis-spring的jar包用来整合。

sqlSessionFactoryBean类实现了接口initalizingbean。我们先看看这个接口做了哪些事情。

public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener;

我们看一下具体的实现

  


   
  1. @Override
  2. public void afterPropertiesSet() throws Exception {
  3. notNull(dataSource, "Property 'dataSource' is required");
  4. notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  5. state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
  6. "Property 'configuration' and 'configLocation' can not specified with together");
  7. //创建sqlSessionFactory
  8. this.sqlSessionFactory = buildSqlSessionFactory();
  9. }
  10. protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
  11. final Configuration targetConfiguration;
  12. XMLConfigBuilder xmlConfigBuilder = null;
  13. //xml解析
  14. if ( this.configuration != null) {
  15. targetConfiguration = this.configuration;
  16. if (targetConfiguration.getVariables() == null) {
  17. targetConfiguration.setVariables( this.configurationProperties);
  18. } else if ( this.configurationProperties != null) {
  19. //设置xml路径等信息
  20. targetConfiguration.getVariables().putAll( this.configurationProperties);
  21. }
  22. } else if ( this.configLocation != null) {
  23. //建一个解析器
  24. xmlConfigBuilder = new XMLConfigBuilder( this.configLocation.getInputStream(), null, this.configurationProperties);
  25. targetConfiguration = xmlConfigBuilder.getConfiguration();
  26. } else {
  27. LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
  28. targetConfiguration = new Configuration();
  29. Optional.ofNullable( this.configurationProperties).ifPresent(targetConfiguration::setVariables);
  30. }
  31. Optional.ofNullable( this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
  32. Optional.ofNullable( this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
  33. Optional.ofNullable( this.vfs).ifPresent(targetConfiguration::setVfsImpl);
  34. if (hasLength( this.typeAliasesPackage)) {
  35. scanClasses( this.typeAliasesPackage, this.typeAliasesSuperType)
  36. .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  37. }
  38. if (!isEmpty( this.typeAliases)) {
  39. Stream.of( this.typeAliases).forEach(typeAlias -> {
  40. targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
  41. LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
  42. });
  43. }
  44. if (!isEmpty( this.plugins)) {
  45. Stream.of( this.plugins).forEach(plugin -> {
  46. targetConfiguration.addInterceptor(plugin);
  47. LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
  48. });
  49. }
  50. if (hasLength( this.typeHandlersPackage)) {
  51. scanClasses( this.typeHandlersPackage, TypeHandler. class).stream()
  52. .filter(clazz -> !clazz.isInterface())
  53. .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
  54. .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
  55. .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
  56. }
  57. if (!isEmpty(this.typeHandlers)) {
  58. Stream.of(this.typeHandlers).forEach(typeHandler -> {
  59. targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
  60. LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
  61. });
  62. }
  63. if ( this.databaseIdProvider != null) { //fix #64 set databaseId before parse mapper xmls
  64. try {
  65. targetConfiguration.setDatabaseId( this.databaseIdProvider.getDatabaseId( this.dataSource));
  66. } catch (SQLException e) {
  67. throw new NestedIOException( "Failed getting a databaseId", e);
  68. }
  69. }
  70. Optional.ofNullable( this.cache).ifPresent(targetConfiguration::addCache);
  71. if (xmlConfigBuilder != null) {
  72. try {
  73. xmlConfigBuilder.parse();
  74. LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
  75. } catch (Exception ex) {
  76. throw new NestedIOException( "Failed to parse config resource: " + this.configLocation, ex);
  77. } finally {
  78. ErrorContext.instance().reset();
  79. }
  80. }
  81. targetConfiguration.setEnvironment(new Environment( this.environment,
  82. this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
  83. this.dataSource));
  84. if ( this.mapperLocations != null) {
  85. if ( this.mapperLocations.length == 0) {
  86. LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
  87. } else {
  88. for (Resource mapperLocation : this.mapperLocations) {
  89. if (mapperLocation == null) {
  90. continue;
  91. }
  92. try {
  93. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
  94. targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
  95. //开始解析了
  96. xmlMapperBuilder.parse();
  97. } catch (Exception e) {
  98. throw new NestedIOException( "Failed to parse mapping resource: '" + mapperLocation + "'", e);
  99. } finally {
  100. ErrorContext.instance().reset();
  101. }
  102. LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
  103. }
  104. }
  105. } else {
  106. LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
  107. }
  108. return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  109. }
  110. //进行xml转类
  111. public void parse() {
  112. if (!configuration.isResourceLoaded(resource)) {
  113. configurationElement(parser.evalNode( "/mapper"));
  114. configuration.addLoadedResource(resource);
  115. //将转出来的类转变为实体
  116. bindMapperForNamespace();
  117. }
  118. parsePendingResultMaps();
  119. parsePendingCacheRefs();
  120. parsePendingStatements();
  121. }

解析xml的


   
  1. private void bindMapperForNamespace() {
  2. String namespace = builderAssistant.getCurrentNamespace();
  3. if ( namespace != null) {
  4. Class boundType = null;
  5. try {
  6. boundType = Resources.classForName( namespace);
  7. } catch (ClassNotFoundException e) {
  8. //ignore, bound type is not required
  9. }
  10. if (boundType != null) {
  11. if (!configuration.hasMapper(boundType)) {
  12. // Spring may not know the real resource name so we set a flag
  13. // to prevent loading again this resource from the mapper interface
  14. // look at MapperAnnotationBuilder#loadXmlResource
  15. configuration.addLoadedResource( "namespace:" + namespace);
  16. //在config中添加到configuration
  17. configuration.addMapper(boundType);
  18. }
  19. }
  20. }

在addMapper方法中

  


   
  1. public void addMapper(Classtype) {
  2. if ( type.isInterface()) {
  3. if (hasMapper( type)) {
  4. throw new BindingException( "Type " + type + " is already known to the MapperRegistry.");
  5. }
  6. boolean loadCompleted = false;
  7. try {
  8. //创建一个代理类
  9. knownMappers.put( type, new MapperProxyFactory<>( type));
  10. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  11.          //缓存起来
  12. parser.parse();
  13. loadCompleted = true;
  14. } finally {
  15. if (!loadCompleted) {
  16. knownMappers.remove( type);
  17. }
  18. }
  19. }
  20. }

这里的parse方法执行结束之后,我们的xml就进入knownMappers 保存。

Map, MapperProxyFactory> knownMappers = new HashMap<>();

但是这个addmapper又是在哪里调用的?

我们发现在MapperFactoryBean中有相关的操作。但是没有发现是谁调用了checkDaoConfig接口,所有去父类看一下。

最后在父类中找到了initalizingBean的接口。于是有和spring有了关系。


   
  1. public abstract class DaoSupport implements InitializingBean {
  2. protected final Log logger = LogFactory.getLog( this.getClass());
  3. public DaoSupport() {
  4. }
  5. public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
  6. //这里进行了调用用
  7.          this.checkDaoConfig();
  8. try {
  9. this.initDao();
  10. } catch (Exception var2) {
  11. throw new BeanInitializationException( "Initialization of DAO failed", var2);
  12. }
  13. }
  14. protected abstract void checkDaoConfig() throws IllegalArgumentException;
  15. protected void initDao() throws Exception {
  16. }
  17. }

而这里的mapperInterface则是我们spring接口扫描的结果。我们还看到在addmapper方法的时候有判断这个接口对应的xml是否已经创建成功了。

同时,我们也发现MapperFactoryBean类也实现了FactoryBean,也就是这里的getObject方法。我们以前学习FactoryBean的时候说如果spring的getBean在内部找不到具体的类的话,就会从其他实现了FactoryBean的接口中获取。那么就这也就是我们开发中使用注解@Autowired注入了一个接口的原因所在。

通过对MapperFactoryBean的学习,我们发现这才是最后的大boss,除此那么既然发生了聚合,那么肯定需要一些基础的东西。于是我们发现

也就是说MapperFactoryBean的时候需要SqlSessionFactory,然后通过sqlsessionFactory创建SqlSessionTemplate。最终调用的还是sqlSessionfactionbean中的configuration,configuration又调用了mapperRegistry。而最终也是通过JDK代理实现的。


   
  1. @SuppressWarnings("unchecked")
  2. protected T newInstance(MapperProxymapperProxy) {
  3. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  4. }
  5. public T newInstance(SqlSession sqlSession) {
  6. final MapperProxymapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  7. return newInstance(mapperProxy);
  8. }

通过上述的分析,我们大概得可以知道mybatis的过程。首先通过spring接口扫描,然后再将扫描到的接口注入到mapperfactorybean中。而sqlSessionFactoryBean也是和spring相关系的,其通过配置文件找到需要解析的xml文件,然后实例化成代理类,并交由knownMappers进行管理。当mapperfactorybean初始化的时候,将扫描到的接口和xml解析的具体实体进行一一对应起来并放入到mapperRegistry中,等待spring注入的时候通过getBean方法进行实例化。能够注入的原因是mapperfactorybean实现了factoryBean接口。

最后我们大概得说一下,这些流程中的关键类,mapperfactorybean是主要的类。其中聚合sqlsessiontemplate,sqlsessiontemplate是从sqlsessionfactory创建而来。sqlsessionfactory又是从sqlsessionfactorybean中而来,sqlsessionfactorybean主要是解析xml文件并将其保存到knownMappers中的。

至此mybatis的主体逻辑大概想清楚了,那么sql的执行过程又是怎么样的?我们下期再看这个问题吧!

参考文献:

https://www.cnblogs.com/hei12138/p/mybatis-spring.html


转载:https://blog.csdn.net/tianjingle_blog/article/details/111503564
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场