[problem analysis] an error is reported when injecting the service layer bindingexception

Time:2019-11-8

problem

@Mapperscan scans the root path of the project. The project contains the service layer interface demoservice and implementation class demoserviceimpl. Injection:

@Autowired
private DemoService demoService;

When demoservice. Insert() is called, an error is reported

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

Analysis

The interrupt point is to view the type of demoservice, which is a mapperproxy. It seems that mybatis failed to find the mapper.xml corresponding to demoservice and reported an error?

And other placesDemoServiceImplInjection is normal. Understand the injection rules of @ Autowired. It is in the same type of interfaceWhen there are multiple implementations, priority is given to inject by attribute name, and only one implementation is injected by type.。 It seems that the reason is that mybatis has registered all interfaces under basepackages as mapperproxy.

Solution

  1. @Mapperscan’s basepackages path is only specified to the mapper layer
  2. @Autorwired injected property name plus impl or @ qualifier

Mapper registration process

  1. Enter @ mapperscan, and a configuration class mapperscannerregistrar is introduced

    @Import(MapperScannerRegistrar.class)
    public @interface MapperScan {
  2. After setting the properties in mapperscan annotation in mapperscannerregister, enter classpathmapperscanner scan bean

    @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //Set annotation properties
        ...
        //Registration filter, not here
        scanner.registerFilters();
        //Scan bean
        scanner.doScan(StringUtils.toStringArray(basePackages));
      }
  3. ClassPathMapperScanner calls the doScan method of the parent class to scan the beanDefinition under the basePackages path, then calls the processBeanDefinitions method to traverse the beanDefinition obtained, sets beanClass to MapperFactoryBean, and sets the constructor parameter to the interface class that is actually scanned.

      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
      //Interface class after construction parameter setting
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      //Beanclass set to mapperfactorybean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
```
  1. Mapperfactorybean implements spring’s factorybean and inherits mybatis’s sqlsessiondaosupport. The former is used to provide the function of getting bean instances and the latter is used to get sqlsessions. Its construction parameter mapperinterface is the above interface class, which is used to obtain the actual mapperproxy according to the interface later. That is to say, mapper is not directly registered in mybatis, but registers a mapperfactorybean. When using it, mapperproxy is obtained from sqlsession through its GetObject method

    //Interface class passed in during construction
      public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      //Get mapperproxy on injection
      @Override
      public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
      }