背景
数据库配置主从之后,如何在代码层面实现读写分离?
用户自定义设置数据库路由
Spring boot提供了AbstractRoutingDataSource根据用户定义的规则选择当前的数据库,这样我们可以在执行查询之前,设置读取从库,在执行完成后,恢复到主库。
实现可动态路由的数据源,在每次数据库查询操作前执行
ReadWriteSplitRoutingDataSource.java
1
2
3
4
5
6
7
8
9
10
11
12
|
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author songrgg
* @since 1.0
*/
public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
|
线程私有路由配置,用于ReadWriteSplitRoutingDataSource动态读取配置
DbContextHolder.java
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
|
/**
* @author songrgg
* @since 1.0
*/
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();
}
}
|
AOP优化代码
利用AOP将设置数据库的操作从代码中抽离,这里的粒度控制在方法级别,所以利用注解的形式标注这个方法涉及的数据库事务只读,走从库。
只读注解,用于标注方法的数据库操作只走从库。
ReadOnlyConnection.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.wallstreetcn.hatano.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates the database operations is bound to the slave database.
* AOP interceptor will set the database to the slave with this interface.
* @author songrgg
* @since 1.0
*/
@Target ({ElementType.METHOD, ElementType.TYPE})
@Retention (RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}
|
ReadOnlyConnectionInterceptor.java
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
|
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
* Intercept the database operations, bind database to read-only database as this annotation
* is applied.
* @author songrgg
* @since 1.0
*/
@Aspect
@Component
public class ReadOnlyConnectionInterceptor implements Ordered {
private 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 ;
}
}
|
UserService.java
1
2
3
4
|
@ReadOnlyConnection
public List<User> getUsers(Integer page, Integer limit) {
return repository.findAll( new PageRequest(page, limit));
}
|
配置Druid数据库连接池
build.gradle
compile("com.alibaba:druid:1.0.18")
groovy依赖注入
配置dataSource为可路由数据源
context.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import com.alibaba.druid.pool.DruidDataSource
import DbContextHolder
import ReadWriteSplitRoutingDataSource
** SOME INITIALIZED CODE LOAD PROPERTIES **
def dataSourceMaster = new DruidDataSource()
dataSourceMaster.url = properties.get( 'datasource.master.url' )
println( "master set to " + dataSourceMaster.url)
dataSourceMaster.username = properties.get( 'datasource.master.username' )
dataSourceMaster.password = properties.get( 'datasource.master.password' )
def dataSourceSlave = new DruidDataSource()
dataSourceSlave.url = properties.get( 'datasource.slave.url' )
println( "slave set to " + dataSourceSlave.url)
dataSourceSlave.username = properties.get( 'datasource.slave.username' )
dataSourceSlave.password = properties.get( 'datasource.slave.password' )
beans {
dataSource(ReadWriteSplitRoutingDataSource) { bean ->
targetDataSources = [
(DbContextHolder.DbType.MASTER): dataSourceMaster,
(DbContextHolder.DbType.SLAVE): dataSourceSlave
]
}
}
|
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://www.cnblogs.com/pier2/p/spring-boot-read-write-split.html