spring boot 实现多数据源读写分离
多数据源之间的关系
修改配置文件
datasource:
slave:
jdbc-url: jdbc:mysql://${S_MYSQL_HOST}:${MYSQL_PORT:3306}/database?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
username: root
password: W212T!@#123qaz
driver-class-name: com.mysql.cj.jdbc.Driver
master:
jdbc-url: jdbc:mysql://${M_MYSQL_HOST}:${MYSQL_PORT:3306}/database?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
username: root
password: 121213456
driver-class-name: com.mysql.cj.jdbc.Driver
自定义DataBase相关类
DataSourceType 数据源枚举类型
public enum DataSourceType {
MASTER,
SLAVE
}
DataSourceContextHolder
import java.util.concurrent.atomic.AtomicInteger;
public class DataSourceContextHolder {
private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();
private static final AtomicInteger counter = new AtomicInteger(-1);
public static void setDataSourceType(DataSourceType dataSourceType) {
contextHolder.set(dataSourceType);
}
public static DataSourceType getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean isMaster() {
return DataSourceType.MASTER.equals(getDataSourceType());
}
public static boolean isSlave() {
return DataSourceType.SLAVE.equals(getDataSourceType());
}
public static DataSourceType randomDataSource() {
int index = counter.getAndIncrement() % DataSourceType.values().length;
if (counter.get() > DataSourceType.values().length) {
counter.set(0);
}
return DataSourceType.values()[index];
}
}
RoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
DataSourceConfig
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ComponentScan("#######需要的这几个bean的包路径########")
@EnableAspectJAutoProxy
public class DataSourceConfig {
@Primary
@Bean("masterDataSource")
@RefreshScope
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource( ) {
return DataSourceBuilder.create().build();
}
@Bean("slaveDataSource")
@RefreshScope
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "routingDataSource")
public DataSource routingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource());
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(masterDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
@Bean(name = "dataSource")
public DataSource dataSource() {
return new LazyConnectionDataSourceProxy(routingDataSource());
}
}
DataSourceAspect
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(-1)
@Slf4j
public class DataSourceAspect {
@Before("execution(* com.app.service.*.add*(..)) " +
"||execution(* com.app.service.*.*insert*(..))" +
"||execution(* com.app.service.*.*edit*(..))" +
"||execution(* com.app.service.*.*update*(..)) " +
"||execution(* com.app.service.*.*save*(..)) " +
"||execution(* com.app.pc.service.*.*del*(..)) "
)
public void masterMethods() {
log.error("----------------switchToMasterDataSource---------------");
DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
}
@Before("execution(* com.app.service.*.get*(..)) " +
"||execution(* com.app.service.*.*select*(..)) " +
"|| execution(* com.app.service.*.*find*(..)) " +
"|| execution(* com.app.service.*.*List*(..)) " )
public void slaveMethods() {
log.error("----------------switchToSlaveDataSource---------------");
DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
}
@After("execution(* com.app.service.*.*(..)) ")
public void clearDataSource(JoinPoint joinPoint) {
log.error("----------------clearDataSource---------------");
DataSourceContextHolder.clearDataSourceType();
}
}
自定义事务管理TransactionManager
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement(order = 1)
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
MyBatisConfig 自定义SqlSessionFactory,否则切换数据源只会成为切换数据源枚举字符串【大坑,目前没查到有人说这一点】
这个分两种情况
- mybatis
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
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 javax.sql.DataSource;
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
//指定xml文件路径
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml"));
return sessionFactoryBean;
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- mybatis-plus
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class MyBatisConfig {
@Bean
public MybatisSqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
// 设置其他属性...
return sessionFactoryBean;
}
}
亲测有效