Skip to content

Commit d48ed1b

Browse files
author
何惠民
committed
Add Issues document
1 parent ec2a668 commit d48ed1b

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

Issues.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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

Comments
 (0)