背景

这是我在2019年7月15日在 mybatis-plus 提的一个issue,当初也不是非得用 mybatis-plus,所以也没有研究为什么,这里补充一下原因。
结论: 自定义AutoConfiguration的装配顺序优先级较低导致注入sqlSessionFactory失败。

补充下人话,Mybatis-plus装配的时候生成DataSource的Configuration还没有运行,所以没有进入

首先,我们有一个自动装配datasource的starter,然后由于MybatisPlusAutoConfiguration 上的ConditionalOnSingleCandidate注解导致未进行自动装配,最终导致了 Property ‘sqlSessionFactory’ or ‘sqlSessionTemplate’ are required 的错误。
image.png
这个是带上了这个 ConditionalOnSingleCandidate(DataSource.class),然后去掉该注解,正常运行。

重现步骤

druid + datasource 的starter

  1. @Configuration
  2. @EnableTransactionManagement(proxyTargetClass = true)
  3. @ConfigurationProperties(prefix = "xxxx.datasource")
  4. @Data
  5. public class DatasourceAutoConfigure {
  6. private Logger logger = LoggerFactory.getLogger(this.getClass());
  7. private String username;
  8. private String password;
  9. private String url;
  10. private String xy;
  11. private String driverClassName;
  12. private String type;
  13. private Integer minIdle;
  14. private Integer initialSize;
  15. private Integer maxActive;
  16. private Integer maxWait;
  17. private String validationQuery;
  18. private Boolean testWhileIdle;
  19. private Boolean testOnBorrow;
  20. private Boolean testOnReturn;
  21. @Autowired(required = false)
  22. private xxxxxxRequest xxxxxxRequest;
  23. @Bean(initMethod = "init", destroyMethod = "close")
  24. public DataSource dataSource() {
  25. RayDruidDataSource druidDataSource = new RayDruidDataSource();
  26. druidDataSource.setUsername(username);
  27. druidDataSource.setPassword(password);
  28. druidDataSource.setUrl(url);
  29. if (StringUtils.hasText(xy)) {
  30. logger.info("load xy:{}", xy);
  31. druidDataSource.setXy(xy);
  32. druidDataSource.setSecretRequest(secretRequest);
  33. }
  34. druidDataSource.setMinIdle(minIdle);
  35. druidDataSource.setInitialSize(initialSize);
  36. druidDataSource.setMaxActive(maxActive);
  37. druidDataSource.setMaxWait(maxWait);
  38. if (druidDataSource.getMaxWait() > -1) {
  39. druidDataSource.setUseUnfairLock(true);
  40. }
  41. druidDataSource.setPoolPreparedStatements(false);
  42. druidDataSource.setValidationQuery(validationQuery);
  43. druidDataSource.setTestWhileIdle(testWhileIdle);
  44. druidDataSource.setTestOnBorrow(testOnBorrow);
  45. druidDataSource.setTestOnReturn(testOnReturn);
  46. return druidDataSource;
  47. }
  48. }

报错信息

加上 @ConditionalOnSingleCandidate(DataSource.class) 导致 MybatisPlusAutoConfiguration 未被加载,最终引发报错。

  1. Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'registerInfoMapper' defined in file [/Users/chenshun/open/register-all/register-dao/target/classes/com/xxxxx/register/dao/mapper/RegisterInfoMapper.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
  2. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778)
  3. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
  4. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
  5. at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
  6. at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
  7. at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
  8. at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
  9. at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:830)
  10. at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
  11. at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
  12. at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
  13. at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
  14. at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
  15. at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127)
  16. at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
  17. at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
  18. ... 24 more
  19. Caused by: java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
  20. at org.springframework.util.Assert.notNull(Assert.java:198)
  21. at org.mybatis.spring.support.SqlSessionDaoSupport.checkDaoConfig(SqlSessionDaoSupport.java:123)
  22. at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:73)
  23. at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
  24. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837)
  25. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774)
  26. ... 39 more

PS:不要使用h2.
我如何解决: 拷贝lib的MybatisPlusAutoConfiguration,移除掉该注解
然后 mybatis-plus粘贴了 mybatis 的自动装配链接,表明这个类也是从 Mybatis copy过来的。
问题结束。

遇到这个问题的人还挺多的。

实际原因

复现(稳定复现)

首先我用了 Mybatis 创建了一个 Demo 项目,运行正常。 随后我用 Mybatis-plus 替换了 Mybatis 的Starter(所有的配置都同步更新为Mybatis-plus),随后遭遇上述错误,根据网络上的大部分博客(博客结论是错误的),引入 mybatis-spring-boot-starter,启动成功,但是使用 QueryWrapper 提示没有这个 statment

寻找原因

  • 加入 mybatis-spring-boot-starter 依赖后启动成功,但是没有 statment 说明没有正常加载 Mybatis-plus 的配置
  • MybatisPlusAutoConfiguration 增加断点,Debug 启动 ,确定没有进来,自此结论确立MybatisPlusAutoConfiguration 没有加载, 而且是由于 @ConditionalOnSingleCandidate(DataSource.class) 注解导致没有加载
    • @ConditionalOnSingleCandidate(DataSource.class) 代表需要一个 DataSource ,如果没有就不进行装配
    • 如果还不确定,可以开启 debug=true ,看一下装配结果
  • 根据上述原因,查找可能的原因,找到原因如下,装配包名

    • mybatis: org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
    • me: io.github.chenshun00.datasource.DatasourceAutoConfigure
    • mybatis-pluse: com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

    • 猜想是由于报名排序以后 c > i > o, 自动装配的优先级是mybatis-plus > me > mybatis , 这就可以解释为什么mybatis可以启动,mybatis-plus不能启动。

  • 验证: 将 io.github.chenshun00.datasource.DatasourceAutoConfigure 更新为 a.b.DatasourceAutoConfigure 发现可以启动成功,都符合预期。 说明猜想正确
  • 启动不起来的原因是由于加载顺序不一致,那么只要将 DatasourceAutoConfigure 的优先级提高即可,@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  • 同时也理解了为什么Mybatis要这么写了

image.png

结论

SpringBoot 的自动装配如果没有被排除exclude掉,那么是会执行的,如果没有执行,说明装配条件不满足,于是问题就从报错现象转移到为什么starter的自动装配没有执行,根据装配现象可以查看是不是跟依赖的先后有联系。