大家好,我是烤鸭:
今天分享springboot读写分离配置。
环境:
springboot 2.1.0.RELEASE
场景说明,目前的需求是 读数据源 * 2 + 写数据源 * 1
application.yml
server:
port: 8085
spring:
application:
name: test-data-test
datasource:
write:
jdbc-url: jdbc:mysql://localhost:3306/test
username: root
password: test.Dev
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
connectionTimeout: 30000
validationTimeout: 5000
maxPoolSize: 200
minIdle: 100
readaw:
jdbc-url: jdbc:mysql://localhost:3306/test
username: root
password: test!i
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
connectionTimeout: 30000
validationTimeout: 5000
maxPoolSize: 200
minIdle: 100
readdc:
jdbc-url: jdbc:mysql://localhost:3306/test
username: root
password: test!i
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
connectionTimeout: 30000
validationTimeout: 5000
maxPoolSize: 200
minIdle: 100
#mybatis
mybatis:
###把xml文件放在com.XX.mapper.*中可能会出现找到的问题,这里把他放在resource下的mapper中
mapper-mapperLocations: classpath*:mapper/**/**/*.xml
type-aliases-package: com.test.test.pojo
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
call-setters-on-nulls: true
useGeneratedKeys: true
DataSourceConfig.java
默认 读数据源,如果需要增加或者减少数据源需要修改 myRoutingDataSource 方法中的参数
package com.test.test.config.db;
import com.test.test.datasource.MyRoutingDataSource;
import com.test.test.datasource.enums.DBTypeEnum;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 关于数据源配置,参考SpringBoot官方文档第79章《Data Access》
* 79. Data Access
* 79.1 Configure a Custom DbSource
* 79.2 Configure Two DataSources
*/
@Configuration
public class DataSourceConfig {
@Autowired
Environment environment;
@Bean
@ConfigurationProperties("spring.datasource.readaw")
public DataSource readDataSourceAw() {
DataSource build = DataSourceBuilder.create().build();
HikariDataSource hikariDataSource = buildDataSource(build,"readaw");
return hikariDataSource;
}
@Bean
@ConfigurationProperties("spring.datasource.readdc")
public DataSource readDataSourceDc() {
DataSource build = DataSourceBuilder.create().build();
HikariDataSource hikariDataSource = buildDataSource(build,"readdc");
return hikariDataSource;
}
@Bean
@ConfigurationProperties("spring.datasource.write")
public DataSource writeDataSource() {
DataSource build = DataSourceBuilder.create().build();
HikariDataSource hikariDataSource = buildDataSource(build,"write");
return hikariDataSource;
}
@Bean
public DataSource myRoutingDataSource(@Qualifier("readDataSourceAw") DataSource readDataSourceAw,
@Qualifier("readDataSourceDc") DataSource readDataSourceDc,
@Qualifier("writeDataSource") DataSource writeDataSource) {
Map
MyBatisConfig.java
注意映射mapper文件路径是在这里修改的,因为重新注入了sqlSession, yml中配置的无效
package com.test.test.config.mybatis;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.sql.DataSource;
@EnableTransactionManagement
@Configuration
public class MyBatisConfig {
@Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(myRoutingDataSource);
}
}
DataSourceAop.java
aop配置类,通过aop的方式限制哪个service的方法连接哪个数据源
目前是根据类上的注解来判断,可以修改为根据方法的注解来判断走哪个数据源
package com.test.test.datasource.aop;
import com.test.test.datasource.annotation.DbSource;
import com.test.test.datasource.handler.DBContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class DataSourceAop {
/**
* 另一种写法:if...else... 判断哪些需要读从数据库,其余的走主数据库
*/
@Before("execution(* com.test.test.service.impl.*.*(..))")
public void before(JoinPoint jp){
MethodSignature methodSignature = (MethodSignature) jp.getSignature();
Method method = methodSignature.getMethod();
System.out.println("拦截到了" + jp.getSignature().getName() +"方法...");
Class> targetClass = jp.getTarget().getClass();
boolean flag = targetClass.isAnnotationPresent(DbSource.class);
//包含数据源注解,数据源为注解中的类
if(flag){
//获取注解的value
DbSource annotation = targetClass.getAnnotation(DbSource.class);
String value = annotation.value();
DBContextHolder.read(value);
}else {
//不包含注解,查询方法默认走 默认读数据源
if (StringUtils.startsWithAny(method.getName(), "get", "select", "find")) {
DBContextHolder.read("");
}else {
DBContextHolder.write();
}
}
}
}
DBTypeEnum.java
数据源枚举,增加和减少数据源修改即可
public enum DBTypeEnum {
READ_AW, READ_DC, WRITE;
}
DBContextHolder.java
数据源切换类,保持当前线程绑定哪个数据源
package com.test.test.datasource.handler;
import com.test.test.datasource.enums.DBTypeEnum;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author gmwang
* @Description // 数据源切换类
* @Date 2019/4/30 9:20
* @Param
* @return
**/
public class DBContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
private static final AtomicInteger counter = new AtomicInteger(-1);
public static void set(DBTypeEnum dbType) {
contextHolder.set(dbType);
}
public static DBTypeEnum get() {
return contextHolder.get();
}
public static void read(String value) {
if(StringUtils.isBlank(value)){
set(DBTypeEnum.READ_AW);
System.out.println("切换到读"+DBTypeEnum.READ_AW.toString());
}
if (DBTypeEnum.READ_DC.toString().equals(value)){
set(DBTypeEnum.READ_DC);
System.out.println("切换到读"+DBTypeEnum.READ_DC.toString());
}
}
public static void write() {
set(DBTypeEnum.WRITE);
System.out.println("切换到写"+DBTypeEnum.WRITE.toString());
}
}
MyRoutingDataSource.java
多数据源的路由类
package com.test.test.datasource;
import com.test.test.datasource.handler.DBContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
/**
* @Author gmwang
* @Description //多数据源的路由
* @Date 2019/4/30 9:38
* @Param
* @return
**/
public class MyRoutingDataSource extends AbstractRoutingDataSource {
/**
* @Author gmwang
* @Description //根据Key获取数据源的信息,上层抽象函数的钩子
* @Date 2019/4/30 9:39
* @Param []
* @return java.lang.Object
**/
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
DbSource
数据源注解,加在serivice实现类上,指定 value,AOP根据注解获取指定的数据源。
package com.test.test.datasource.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DbSource {
String value();
}
例如本例中,默认读库是 READ_AW ,如果不加注解默认,读取默认库。如果指定注解 READ_DC,就用指定的数据源。
伪代码:
在tes方法中使用查询(不同的库)后插入操作,结果如图所示。