问题
@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了。
解决方案
- @MapperScan的basePackages路径只指定到mapper层
- @Autorwired注入的属性名加上impl,或加上@Qualifier一起使用
Mapper注册流程
-
进入@MapperScan,发现引入了一个配置类MapperScannerRegistrar
@Import(MapperScannerRegistrar.class) public @interface MapperScan {
-
MapperScannerRegistrar里设置了MapperScan注解里的属性之后,进入ClassPathMapperScanner扫描bean
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 设置注解属性 ... // 注册过滤器,这里没有 scanner.registerFilters(); // 扫描bean scanner.doScan(StringUtils.toStringArray(basePackages)); }
-
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());
```
-
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); }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。