背景
数据库配置主从之后,如何在代码层面实现读写分离?
用户自定义设置数据库路由
Spring boot提供了AbstractRoutingDataSource根据用户定义的规则选择当前的数据库,这样我们可以在执行查询之前,设置读取从库,在执行完成后,恢复到主库。
实现可动态路由的数据源,在每次数据库查询操作前执行
ReadWriteSplitRoutingDataSource.java
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
线程私有路由配置,用于ReadWriteSplitRoutingDataSource动态读取配置
DbContextHolder.java
public class DbContextHolder {
public enum DbType {
MASTER,
SLAVE
}
private static final ThreadLocal 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
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;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadonlyConnection {
}
ReadOnlyConnectionInterceptor.java
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;
@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
@ReadonlyConnection public ListgetUsers(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
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
]
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。



