Hello everyone, I am a duck:
Share springboot separate read and write configuration today.
surroundings:
springboot 2.1.0.RELEASE
Scene shows that the current demand is reading data sources * 2 * 1 + write data source
1. Profiles
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
2. Configure class
DataSourceConfig.java
The default read data source, if the data source needs to be increased or decreased to modify the parameters of the method 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<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.READ_AW, readDataSourceAw);
targetDataSources.put(DBTypeEnum.READ_DC, readDataSourceDc);
targetDataSources.put(DBTypeEnum.WRITE, writeDataSource);
MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
myRoutingDataSource.setDefaultTargetDataSource(readDataSourceAw);
myRoutingDataSource.setTargetDataSources(targetDataSources);
return myRoutingDataSource;
}
public HikariDataSource buildDataSource(DataSource dataSource,String dataSourcePrefix){
HikariDataSource hikariDataSource= (HikariDataSource) dataSource;
hikariDataSource.setDriverClassName(environment.getProperty("spring.datasource."+dataSourcePrefix+".driver-class-name"));
hikariDataSource.setJdbcUrl(environment.getProperty("spring.datasource."+dataSourcePrefix+".jdbc-url"));
hikariDataSource.setUsername(environment.getProperty("spring.datasource."+dataSourcePrefix+".username"));
hikariDataSource.setPassword(environment.getProperty("spring.datasource."+dataSourcePrefix+".password"));
hikariDataSource.setMinimumIdle(Integer.parseInt(environment.getProperty("spring.datasource."+dataSourcePrefix+".minIdle")));
hikariDataSource.setConnectionTimeout(Long.parseLong(environment.getProperty("spring.datasource."+dataSourcePrefix+".connectionTimeout")));
hikariDataSource.setValidationTimeout(Long.parseLong(environment.getProperty("spring.datasource."+dataSourcePrefix+".validationTimeout")));
hikariDataSource.setMaximumPoolSize(Integer.parseInt(environment.getProperty("spring.datasource."+dataSourcePrefix+".maxPoolSize")));
return hikariDataSource;
}
}
MyBatisConfig.java
Note that the file path is mapped mapper modified here, since re-injected sqlSession, invalid yml configured
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 configuration class, which limit the service by connecting aop ways which data source
the current annotation is based on the class judgment, which can be modified according to the data source is determined to take the annotated method
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
Data source enumeration, increase and decrease data sources can be modified
public enum DBTypeEnum {
READ_AW, READ_DC, WRITE;
}
DBContextHolder.java
Data source switching classes, which holds the current thread-bound data source
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<DBTypeEnum> 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
Routing multiple data sources
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
Annotation data source, applied to the serivice implementation classes specified value, AOP acquired annotation in accordance with the specified data source.
package com.test.test.datasource.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DbSource {
String value();
}
For example in this embodiment, the default library is read READ_AW, if not the default annotation, reading default library. If you specify annotation READ_DC, to use the specified data source.
3. Test results
Fake code:
Using the query (different libraries) tes insert operation method, the results shown in FIG.