[问题分析]注入service层时报错BindingException

Java基础

浏览数:96

2019-9-7

AD:资源代下载服务

问题

@MapperScan扫描项目根路径,项目中有服务层接口DemoService和实现类DemoServiceImpl,注入:

@Autowired
private DemoService demoService;

则调用demoService.insert()时,报错

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): xxx.service.DemoService.insert

分析

打断点查看demoService的类型,是一个MapperProxy,看报错提示似乎是mybatis找不到DemoService对应的Mapper.xml而报的错?

而其他地方使用DemoServiceImpl注入则是正常的,了解一下@Autowired的注入规则,是在同类型的接口有多个实现时优先按属性名注入,只有一个实现时按类型注入。看来原因是Mybatis把basePackages下所有接口都注册成MapperProxy了。

解决方案

  1. @MapperScan的basePackages路径只指定到mapper层
  2. @Autorwired注入的属性名加上impl,或加上@Qualifier一起使用

Mapper注册流程

  1. 进入@MapperScan,发现引入了一个配置类MapperScannerRegistrar

    @Import(MapperScannerRegistrar.class)
    public @interface MapperScan {
  2. MapperScannerRegistrar里设置了MapperScan注解里的属性之后,进入ClassPathMapperScanner扫描bean

      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 设置注解属性
        ...
        // 注册过滤器,这里没有
        scanner.registerFilters();
        // 扫描bean
        scanner.doScan(StringUtils.toStringArray(basePackages));
      }
  3. ClassPathMapperScanner调用父类的doScan方法扫描basePackages路径下的beanDefinition,然后调用processBeanDefinitions方法遍历得到的beanDefinition,将beanClass设置为MapperFactoryBean,再将构造参数设置为实际扫描到的接口类

      private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
    
          if (logger.isDebugEnabled()) {
            logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      //构造参数设置后接口类
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      // beanClass设置为MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
```
  1. MapperFactoryBean实现了Spring的FactoryBean,继承了Mybatis的SqlSessionDaoSupport,前者用于提供获取bean实例的功能,后者用于获取sqlSession。其构造参数mapperInterface就是上述的接口类,后续用于根据接口获取实际的MapperProxy。即,在Mybatis中Mapper并不是直接注册出来,而是注册一个MapperFactoryBean,使用时再通过其getObject方法从SqlSession中获取接口代理MapperProxy

      // 构造时传入接口类
      public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      // 注入时获取MapperProxy
      @Override
      public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
      }

作者:BanpilZ