文将介绍使用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