|
| 1 | +# 在使用 Spring Boot 和 MyBatis 动态切换数据源时遇到的问题以及解决方法 |
| 2 | + |
| 3 | +> 相关项目地址:[https://github.com/helloworlde/SpringBoot-DynamicDataSource](https://github.com/helloworlde/SpringBoot-DynamicDataSource) |
| 4 | + |
| 5 | +## 1. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) |
| 6 | + |
| 7 | +> 在使用了动态数据源后遇到了该问题,从错误信息来看是因为没有找到 `*.xml` 文件而导致的,但是在配置文件中 |
| 8 | +确实添加了相关的配置,这种错误的原因是因为设置数据源后没有设置`SqlSessionFactoryBean`的 `typeAliasesPackage` |
| 9 | +和`mapperLocations`属性或属性无效导致的; |
| 10 | + |
| 11 | +- 解决方法: |
| 12 | + |
| 13 | +> 如果在应用的入口类中添加了 `@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)`, |
| 14 | +在`DataSourceConfigure`类的中设置相关属性: |
| 15 | + |
| 16 | +```java |
| 17 | + @Bean |
| 18 | + @ConfigurationProperties(prefix = "mybatis") |
| 19 | + public SqlSessionFactoryBean sqlSessionFactoryBean() { |
| 20 | + SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); |
| 21 | + sqlSessionFactoryBean.setDataSource(dynamicDataSource()); |
| 22 | + return sqlSessionFactoryBean; |
| 23 | + } |
| 24 | +``` |
| 25 | + |
| 26 | +或者直接配置(不推荐该方式): |
| 27 | + |
| 28 | +```java |
| 29 | + @Bean |
| 30 | + public SqlSessionFactoryBean sqlSessionFactoryBean() { |
| 31 | + SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); |
| 32 | + sqlSessionFactoryBean.setTypeAliasesPackage("typeAliasesPackage"); |
| 33 | + sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("mapperLocations")); |
| 34 | + sqlSessionFactoryBean.setDataSource(dynamicDataSource()); |
| 35 | + return sqlSessionFactoryBean; |
| 36 | + } |
| 37 | +``` |
| 38 | + |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +## 2. Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed |
| 44 | + |
| 45 | +> 该异常在错误信息中已经说的很清楚了,是因为有多个 `DataSource` 的实例,所以无法确定该引用那个实例 |
| 46 | + |
| 47 | +- 解决方法: |
| 48 | + |
| 49 | +> 为数据源的某个 `Bean` 添加 `@Primary` 注解,该 `Bean` 应当是通过 `DataSourceBuilder.create().build()` |
| 50 | +得到的 `Bean`,而不是通过 `new AbstractRoutingDataSource` 的子类实现的 `Bean`,在本项目中可以是 `master()` |
| 51 | +或 `slave()` 得到的 `DataSource`,不能是 `dynamicDataSource()` 得到的 `DataSource` |
| 52 | + |
| 53 | +## 3. 通过注解方式动态切换数据源无效 |
| 54 | + |
| 55 | +- 请确认注解没有放到 DAO 层方法上, 因为会在 Service 层开启事务,所以当注解在 DAO 层时不会生效 |
| 56 | +- 请确认以下 `Bean` 正确配置: |
| 57 | + |
| 58 | +```java |
| 59 | + @Bean("dynamicDataSource") |
| 60 | + public DataSource dynamicDataSource() { |
| 61 | + DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); |
| 62 | + Map<Object, Object> dataSourceMap = new HashMap<>(2); |
| 63 | + dataSourceMap.put("master", master()); |
| 64 | + dataSourceMap.put("slave", slave()); |
| 65 | + |
| 66 | + // Set master datasource as default |
| 67 | + dynamicRoutingDataSource.setDefaultTargetDataSource(master()); |
| 68 | + // Set master and slave datasource as target datasource |
| 69 | + dynamicRoutingDataSource.setTargetDataSources(dataSourceMap); |
| 70 | + |
| 71 | + // To put datasource keys into DataSourceContextHolder to judge if the datasource is exist |
| 72 | + DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet()); |
| 73 | + return dynamicRoutingDataSource; |
| 74 | + } |
| 75 | + |
| 76 | + @Bean |
| 77 | + @ConfigurationProperties(prefix = "mybatis") |
| 78 | + public SqlSessionFactoryBean sqlSessionFactoryBean() { |
| 79 | + SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); |
| 80 | + // Here is very important, if don't config this, will can't switch datasource |
| 81 | + // put all datasource into SqlSessionFactoryBean, then will autoconfig SqlSessionFactory |
| 82 | + sqlSessionFactoryBean.setDataSource(dynamicDataSource()); |
| 83 | + return sqlSessionFactoryBean; |
| 84 | + } |
| 85 | + |
| 86 | +``` |
| 87 | + |
| 88 | +## 4. `@Transactional` 注解无效,发生异常不回滚 |
| 89 | + |
| 90 | +- 请确认该 `Bean` 得到正确配置,并且`@Transactional` 的 `rollbackFor` 配置正确 |
| 91 | + |
| 92 | +```java |
| 93 | + @Bean |
| 94 | + public PlatformTransactionManager transactionManager() { |
| 95 | + return new DataSourceTransactionManager(dynamicDataSource()); |
| 96 | + } |
| 97 | + |
| 98 | +``` |
| 99 | + |
| 100 | +## 5. 通过 AOP 判断 DAO 层方法名时切换数据源无效 |
| 101 | + |
| 102 | +> 当切面指向了 DAO 层后无论如何设置切面的顺序,都无法在执行查询之前切换数据源,但是切面改为 Service 层后可以正常工作 |
| 103 | + |
| 104 | +- 解决方法: 请确认 `@Transactional` 注解是加在方法上而不是 Service 类上,添加了 `@Transactional` 的方法因为在 Service 层开启了事务, |
| 105 | +会在事务结束之后才会切换数据源 |
| 106 | + |
| 107 | +- 检出 `DataSourceTransactionManager` Bean 注入正确 |
| 108 | + |
| 109 | +```java |
| 110 | + @Bean |
| 111 | + public PlatformTransactionManager transactionManager() { |
| 112 | + return new DataSourceTransactionManager(dynamicDataSource()); |
| 113 | + } |
| 114 | +``` |
| 115 | + |
| 116 | +## 6. The dependencies of some of the beans in the application context form a cycle |
| 117 | + |
| 118 | +- 错误信息: |
| 119 | + |
| 120 | +``` |
| 121 | +The dependencies of some of the beans in the application context form a cycle: |
| 122 | + |
| 123 | + produceController (field private cn.com.hellowood.dynamicdatasource.service.ProductService cn.com.hellowood.dynamicdatasource.controller.ProduceController.productService) |
| 124 | + ↓ |
| 125 | + productService (field private cn.com.hellowood.dynamicdatasource.mapper.ProductDao cn.com.hellowood.dynamicdatasource.service.ProductService.productDao) |
| 126 | + ↓ |
| 127 | + productDao defined in file [/Users/hehuimin/Downloads/Dev/SpringBoot/DynamicDataSource/out/production/classes/cn/com/hellowood/dynamicdatasource/mapper/ProductDao.class] |
| 128 | + ↓ |
| 129 | + sqlSessionFactoryBean defined in class path resource [cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.class] |
| 130 | +┌─────┐ |
| 131 | +| dynamicDataSource defined in class path resource [cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.class] |
| 132 | +↑ ↓ |
| 133 | +| master defined in class path resource [cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.class] |
| 134 | +↑ ↓ |
| 135 | +| dataSourceInitializer |
| 136 | +└─────┘ |
| 137 | +``` |
| 138 | + |
| 139 | +> 这是因为在注入 `DataSource` 的实例的时候产生了循环调用,第一个注入的 Bean 依赖于其他的 Bean, 而被依赖的 Bean 产生依赖传递,依赖第一个 |
| 140 | +注入的 Bean, 陷入了循环,无法启动项目 |
| 141 | + |
| 142 | +- 解决方法:将 `@Primary` 注解指向没有依赖的 Bean,如: |
| 143 | +```java |
| 144 | + |
| 145 | + /** |
| 146 | + * master DataSource |
| 147 | + * @Primary 注解用于标识默认使用的 DataSource Bean,因为有三个 DataSource Bean,该注解可用于 master |
| 148 | + * 或 slave DataSource Bean, 但不能用于 dynamicDataSource Bean, 否则会产生循环调用 |
| 149 | + * |
| 150 | + * @ConfigurationProperties 注解用于从 application.properties 文件中读取配置,为 Bean 设置属性 |
| 151 | + * @return data source |
| 152 | + */ |
| 153 | + @Bean("master") |
| 154 | + @Primary |
| 155 | + @ConfigurationProperties(prefix = "application.server.db.master") |
| 156 | + public DataSource master() { |
| 157 | + return DataSourceBuilder.create().build(); |
| 158 | + } |
| 159 | + |
| 160 | + @Bean("slave") |
| 161 | + @ConfigurationProperties(prefix = "application.server.db.slave") |
| 162 | + public DataSource slave() { |
| 163 | + return DataSourceBuilder.create().build(); |
| 164 | + } |
| 165 | + |
| 166 | + @Bean("dynamicDataSource") |
| 167 | + public DataSource dynamicDataSource() { |
| 168 | + DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); |
| 169 | + Map<Object, Object> dataSourceMap = new HashMap<>(2); |
| 170 | + dataSourceMap.put("master", master()); |
| 171 | + dataSourceMap.put("slave", slave()); |
| 172 | + |
| 173 | + // Set master datasource as default |
| 174 | + dynamicRoutingDataSource.setDefaultTargetDataSource(master()); |
| 175 | + // Set master and slave datasource as target datasource |
| 176 | + dynamicRoutingDataSource.setTargetDataSources(dataSourceMap); |
| 177 | + |
| 178 | + // To put datasource keys into DataSourceContextHolder to judge if the datasource is exist |
| 179 | + DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet()); |
| 180 | + return dynamicRoutingDataSource; |
| 181 | + } |
| 182 | +``` |
0 commit comments