文將介紹使用Spring Boot集成Mybatis並實現主從庫分離的實現(同樣適用於多數據源)。延續之前的Spring Boot 集成MyBatis。項目還將集成分頁插件PageHelper、通用Mapper以及Druid。
新建一個Maven項目,最終項目結構如下:
多數據源注入到sqlSessionFactory
POM增加如下依賴:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
<!--JSON-->
<
dependency
>
<
groupId
>com.fasterxml.jackson.core</
groupId
>
<
artifactId
>jackson-core</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>com.fasterxml.jackson.core</
groupId
>
<
artifactId
>jackson-databind</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>com.fasterxml.jackson.datatype</
groupId
>
<
artifactId
>jackson-datatype-joda</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>com.fasterxml.jackson.module</
groupId
>
<
artifactId
>jackson-module-parameter-names</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-jdbc</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>mysql</
groupId
>
<
artifactId
>mysql-connector-java</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>com.alibaba</
groupId
>
<
artifactId
>druid</
artifactId
>
<
version
>1.0.11</
version
>
</
dependency
>
<!--mybatis-->
<
dependency
>
<
groupId
>org.mybatis.spring.boot</
groupId
>
<
artifactId
>mybatis-spring-boot-starter</
artifactId
>
<
version
>1.1.1</
version
>
</
dependency
>
<!--mapper-->
<
dependency
>
<
groupId
>tk.mybatis</
groupId
>
<
artifactId
>mapper-spring-boot-starter</
artifactId
>
<
version
>1.1.0</
version
>
</
dependency
>
<!--pagehelper-->
<
dependency
>
<
groupId
>com.github.pagehelper</
groupId
>
<
artifactId
>pagehelper-spring-boot-starter</
artifactId
>
<
version
>1.1.0</
version
>
<
exclusions
>
<
exclusion
>
<
artifactId
>mybatis-spring-boot-starter</
artifactId
>
<
groupId
>org.mybatis.spring.boot</
groupId
>
</
exclusion
>
</
exclusions
>
</
dependency
>
|
這里需要注意的是:項目是通過擴展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來實現多數據源注入的。在mybatis-spring-boot-starter:1.2.0中,該類取消了默認構造函數,因此本項目依舊使用1.1.0版本。需要關注后續版本是否會重新把擴展開放處理。
之所以依舊使用舊方案,是我個人認為開放擴展是合理的,相信在未來的版本中會回歸。
如果你需要其他方案可參考傳送門
增加主從庫配置(application.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
druid:
type: com.alibaba.druid.pool.DruidDataSource
master:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
initial-size: 5
min-idle: 1
max-active: 100
test-on-borrow: true
slave:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
initial-size: 5
min-idle: 1
max-active: 100
test-on-borrow: true
|
創建數據源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Configuration
@EnableTransactionManagement
public
class
DataSourceConfiguration {
@Value
(
"${druid.type}"
)
private
Class<?
extends
DataSource> dataSourceType;
@Bean
(name =
"masterDataSource"
)
@Primary
@ConfigurationProperties
(prefix =
"druid.master"
)
public
DataSource masterDataSource(){
return
DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean
(name =
"slaveDataSource"
)
@ConfigurationProperties
(prefix =
"druid.slave"
)
public
DataSource slaveDataSource1(){
return
DataSourceBuilder.create().type(dataSourceType).build();
}
}
|
將多數據源注入到sqlSessionFactory中
前面提到了這里通過擴展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來實現多數據源注入的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Configuration
@AutoConfigureAfter
({DataSourceConfiguration.
class
})
public
class
MybatisConfiguration
extends
MybatisAutoConfiguration {
private
static
Log logger = LogFactory.getLog(MybatisConfiguration.
class
);
@Resource
(name =
"masterDataSource"
)
private
DataSource masterDataSource;
@Resource
(name =
"slaveDataSource"
)
private
DataSource slaveDataSource;
@Bean
public
SqlSessionFactory sqlSessionFactory()
throws
Exception {
return
super
.sqlSessionFactory(roundRobinDataSouceProxy());
}
public
AbstractRoutingDataSource roundRobinDataSouceProxy(){
ReadWriteSplitRoutingDataSource proxy =
new
ReadWriteSplitRoutingDataSource();
Map<Object,Object> targetDataResources =
new
ClassLoaderRepository.SoftHashMap();
targetDataResources.put(DbContextHolder.DbType.MASTER,masterDataSource);
targetDataResources.put(DbContextHolder.DbType.SLAVE,slaveDataSource);
proxy.setDefaultTargetDataSource(masterDataSource);
//默認源
proxy.setTargetDataSources(targetDataResources);
return
proxy;
}
}
|
實現讀寫分離(多數據源分離)
這里主要思路如下:
1-將不同的數據源標識記錄在ThreadLocal中
2-通過注解標識出當前的service方法使用哪個庫
3-通過Spring AOP實現攔截注解並注入不同的標識到threadlocal中
4-獲取源的時候通過threadlocal中不同的標識給出不同的sqlSession
標識存放ThreadLocal的實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
class
DbContextHolder {
public
enum
DbType{
MASTER,SLAVE
}
private
static
final
ThreadLocal<DbType> contextHolder =
new
ThreadLocal<>();
public
static
void
setDbType(DbType dbType){
if
(dbType==
null
)
throw
new
NullPointerException();
contextHolder.set(dbType);
}
public
static
DbType getDbType(){
return
contextHolder.get()==
null
?DbType.MASTER:contextHolder.get();
}
public
static
void
clearDbType(){
contextHolder.remove();
}
}
|
注解實現
1
2
3
4
5
6
7
8
|
/**
* 該注解注釋在service方法上,標注為鏈接slaves庫
* Created by Jason on 2017/3/6.
*/
@Target
({ElementType.METHOD,ElementType.TYPE})
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
ReadOnlyConnection {
}
|
Spring AOP對注解的攔截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Aspect
@Component
public
class
ReadOnlyConnectionInterceptor
implements
Ordered {
public
static
final
Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.
class
);
@Around
(
"@annotation(readOnlyConnection)"
)
public
Object proceed(ProceedingJoinPoint proceedingJoinPoint,ReadOnlyConnection readOnlyConnection)
throws
Throwable {
try
{
logger.info(
"set database connection to read only"
);
DbContextHolder.setDbType(DbContextHolder.DbType.SLAVE);
Object result = proceedingJoinPoint.proceed();
return
result;
}
finally
{
DbContextHolder.clearDbType();
logger.info(
"restore database connection"
);
}
}
@Override
public
int
getOrder() {
return
0
;
}
}
|
根據標識獲取不同源
這里我們通過擴展AbstractRoutingDataSource來獲取不同的源。它是Spring提供的一個可以根據用戶發起的不同請求去轉換不同的數據源,比如根據用戶的不同地區語言選擇不同的數據庫。通過查看源碼可以發現,它是通過determineCurrentLookupKey()返回的不同key到sqlSessionFactory中獲取不同源(前面已經展示了如何在sqlSessionFactory中注入多個源)
1
2
3
4
5
6
7
|
public
class
ReadWriteSplitRoutingDataSource
extends
AbstractRoutingDataSource {
@Override
protected
Object determineCurrentLookupKey() {
return
DbContextHolder.getDbType();
}
}
|
以上就完成了讀寫分離(多數據源)的配置方案。下面是一個具體的實例
使用方式
Entity
1
2
3
4
5
6
7
8
9
10
11
|
@Table
(name =
"t_sys_dic_type"
)
public
class
DicType
extends
BaseEntity{
String code;
String name;
Integer status;
...
}
|
Mapper
1
2
|
public
interface
DicTypeMapper
extends
BaseMapper<DicType> {
}
|
Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Service
public
class
DicTypeService {
@Autowired
private
DicTypeMapper dicTypeMapper;
@ReadOnlyConnection
public
List<DicType> getAll(DicType dicType){
if
(dicType.getPage() !=
null
&& dicType.getRows() !=
null
) {
PageHelper.startPage(dicType.getPage(), dicType.getRows());
}
return
dicTypeMapper.selectAll();
}
}
|
注意這里的@ReadOnlyConnection注解
Controller
1
2
3
4
5
6
7
8
9
10
11
12
|
@RestController
@RequestMapping
(
"/dictype"
)
public
class
DicTypeController {
@Autowired
private
DicTypeService dicTypeService;
@RequestMapping
(value =
"/all"
)
public
PageInfo<DicType> getALL(DicType dicType){
List<DicType> dicTypeList = dicTypeService.getAll(dicType);
return
new
PageInfo<>(dicTypeList);
}
}
|
通過mvn spring-boot:run啟動后,即可通過http://localhost:9090/dictype/all 獲取到數據
后台打印出
1
|
c.a.d.m.ReadOnlyConnectionInterceptor : set database connection to read only
|
說明使用了從庫的鏈接獲取數據
備注:如何保證多源事務呢?
1-在讀寫分離場景中不會考慮主從庫事務,在純讀的上下文上使用@ReadOnlyConnection標簽。其他則默認使用主庫。
2-在多源場景中,Spring的@Transaction是可以保證多源的事務性的。
Spring Boot+Mybatis+Druid+PageHelper實現多數據源並分頁的方法
原文鏈接:http://www.jianshu.com/p/8813ec02926a