Mybatis Annotation使用小结

Mybatis Annotation使用小结

之前一直有看过mybatis的注解使用方式,但没有去看过它的原理。今天看springboot-mybatis-annotation使用的时候,debug了下看了它的加载过程。

  1. 先编写一个示例接口

    @Mapper // 标志为 Mybatis 的 Mapper
    public interface CityDao {
    
        /**
         * 根据城市名称,查询城市信息
         *
         * @param cityName 城市名
         */
        @Select("SELECT * FROM city")
        // 返回 Map 结果集
        @Results({
                @Result(property = "id", column = "id"),
                @Result(property = "provinceId", column = "province_id"),
                @Result(property = "cityName", column = "city_name"),
                @Result(property = "description", column = "description"),
        })
        City findByName(@Param("cityName") String cityName);
    }
    
  2. springboot引入mybatis-spring-boot-starter依赖

    <dependency>
    			<groupId>org.mybatis.spring.boot</groupId>
    			<artifactId>mybatis-spring-boot-starter</artifactId>
    			<version>${mybatis-spring-boot}</version>
    </dependency>
    

    它会自动注入MybatisAutoConfiguration这个bean然后解析执行AutoConfiguredMapperScannerRegistrar这个ImportBeanDefinitionRegistrar,它的registerBeanDefinitions方法会扫描包把带有Mapper注解的接口注入到spring容器,并且在内部修改BeanDefinition的class为MapperFactoryBean,并且设置它的自动注入模式为AUTOWIRE_BY_TYPE,实现了SqlSessionFactory的自动注入。

    @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
          logger.debug("Searching for mappers annotated with @Mapper");
    
          ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    
          try {
            if (this.resourceLoader != null) {
              scanner.setResourceLoader(this.resourceLoader);
            }
    
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
            if (logger.isDebugEnabled()) {
              for (String pkg : packages) {
                logger.debug("Using auto-configuration base package '{}'", pkg);
              }
            }
    
            scanner.setAnnotationClass(Mapper.class);
            scanner.registerFilters();
            scanner.doScan(StringUtils.toStringArray(packages));
          } catch (IllegalStateException ex) {
            logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
          }
        }
    
  3. MapperFactoryBean的实例化

    MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport继承了DaoSupport,DaoSupport它实现了InitializingBean,所以实例化MapperFactoryBean的时候会调用afterPropertiesSet方法。
    
    	   @Override
    		public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    			// Let abstract subclasses check their configuration.
    			checkDaoConfig();
    	
    			// Let concrete implementations initialize themselves.
    			try {
    				initDao();
    			}
    			catch (Exception ex) {
    				throw new BeanInitializationException("Initialization of DAO failed", ex);
    			}
    		}
    

    这里会调用MapperFactoryBean的重写方法checkDaoConfig

      protected void checkDaoConfig() {
        super.checkDaoConfig();
    
        notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    
        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();
          }
        }
      }
    
	  因为配置了方法注解所以我们在不配置xml的时候configuration里并没有对应的mapperRegistry,所以会调用configuration的addMapper方法,内部会调用mapperRegistry的addMapper方法
	  
	  
	```
	  public <T> void addMapper(Class<T> type) {
	    if (type.isInterface()) {
	      if (hasMapper(type)) {
	        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
	      }
	      boolean loadCompleted = false;
	      try {
	        knownMappers.put(type, new MapperProxyFactory<T>(type));
	        // It's important that the type is added before the parser is run
	        // otherwise the binding may automatically be attempted by the
	        // mapper parser. If the type is already known, it won't try.
	        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
	        parser.parse();
	        loadCompleted = true;
	      } finally {
	        if (!loadCompleted) {
	          knownMappers.remove(type);
	        }
	      }
	    }
	  }
	```
		
	从以上代码可知通过MapperAnnotationBuilder这个类去解析当前接口的方法上的配置信息转化成MappedStatement并设置到全局的Configuration里面。这样就和直接解析xml配置达到了一样的效果。
	
4.	MapperFactoryBean生成的动态代理bean的调用
	
	MapperFactoryBean生成的动态代理bean,调用方法的过程最终会委派给MapperProxy的invoke方法,后续的调用过程就和xml解析出来的配置一致了,也就是说不管是注解配置还是xml配置只是解析配置的方式不同,最终都会设置到Configuration里面。然而调用过程一致。
	
---
代码下载参考[springboot-learning-example](https://github.com/yaojf/springboot-learning-example)
原文地址:https://www.cnblogs.com/yaojf/p/7598560.html