一、 多数据源配置信息
在properties配置文件中配置master 和 slave两个数据源
#########Master数据源配置###########
spring.master.datasource.name=master
spring.master.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.master.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.master.datasource.url=jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.master.datasource.username=master
spring.master.datasource.password=123456
#########Slave数据源配置###########
spring.slave.datasource.name=slave
spring.slave.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.slave.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.slave.datasource.url=jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.slave.datasource.username=slave
spring.slave.datasource.password=123456
二、定义一个类继承AbstractRoutingDataSource
定义一个DynamicDataSource 类继承AbstractRoutingDataSource实现动态数据源,在类中定义一个ThreadLocal<String> 变量存储当前线程使用的数据源名称,用于方法determineCurrentLookupKey()获取当前数据源名称。
/**
* 自定义动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource implements InitializingBean {
private static ThreadLocal<String> dataSourceName = new ThreadLocal<>();
@Override
protected Object determineCurrentLookupKey() {
return dataSourceName.get();
}
public static void setDataSource(String dataSourceType) {
dataSourceName.set(dataSourceType);
}
public static String getDataSource() {
return dataSourceName.get();
}
//bean对象初始化操作
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
}
三、使用@Configuration定义多数据源配置类,手动注册数据源到Spring容器中
@Bean 注解向Spring容器中注册masterDataSource、 slaveDataSource、 DynamicDataSource 三个数据源,其中DynamicDataSource 数据源上方要加上@Primary注解,使其成为主数据源。这样TransactionManager和SqlSessionFactory才知道使用哪一个数据源。
/**
* 定义主从数据源枚举类
*/
public enum DataSourceType {
MASTER, //主数据库
SLAVE; //从数据库
}
/**
* 多数据源配置类
*/
@Configuration
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties(prefix="spring.master.datasource")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix="spring.slave.datasource")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 多数据源, 真正关联TransactionManager和SqlSessionFactory
*/
@Bean
@Primary //primary定义主数据源,以便TransactionManager和SqlSessionFactory能确定自动注入数据源
public DynamicDataSource dataSource() {
DynamicDataSource dds = new DynamicDataSource();
//多数据源数据源注入
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name().toLowerCase(), this.masterDataSource());
targetDataSources.put(DataSourceType.SLAVE.name().toLowerCase(), this.slaveDataSource());
dds.setTargetDataSources(targetDataSources);
//设置默认数据源
dds.setDefaultTargetDataSource(this.masterDataSource());
return dds;
}
}
四、使用AOP 拦截service方法,确定service方法用哪一个数据源
定义一个DataSource注解类,用于标记service方法使用的数据源名称
public @interface DataSource {
/**
* 数据源名称
*/
String name() default "master";
}
定义一个AOP类拦截DataSource注解,获取数据源名称,然后动态的设置DynamicDataSource中ThreadLocal变量存储当前线程使用数据源名称
AopDataSource类上方需要用@Order注解标注,用于设置注解拦截顺序,确保我们自定义的AOP类能在@Transactional事务拦截之前生效。
/**
*aop方式拦截数据源注解
*/
@Configuration
@Aspect
@Order(Integer.MAX_VALUE-5) //设置注解拦截顺序,用于在transaction之前生效
public class AopDataSource {
@Pointcut("within(cn.demo.datasource.service.impl.*) && @annotation(DataSource)")
public void pointCut() { }
@Before("pointCut()")
public void before(JoinPoint jp) {
MethodSignature sign = (MethodSignature) jp.getSignature();
Method method = sign.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
//往ThreadLocal中设置数据源名称
DynamicDataSource.setDataSource(ds.name());
}
}
五、在Service类方法上标注@DataSource和@Transactional注解,用于数据源切换和事务控制
若要实现主数据源和从数据源事务同时回滚,需要定义两个方法 :
- insertMaster()方法执行master主数据库操作,insertSlave()方法执行slave从数据库操作。
- 两个方法上方都要标注@DataSource和@Transactional注解,
- insertSlave()方法@Transactional注解需要设置事务传播方式propagation=Propagation.REQUIRES_NEW
- insertMaster()方法中调用 insertSlave()方法
这样insertMaster方法会使用master事务,insertSlave会使用slave事务,无论在两个方法中哪个地方报错抛出异常都会使master和slave事务同时回滚。
@Service
public class DataSourceServiceImpl implements IDataSourceService, ApplicationContextAware {
public ApplicationContext applicationContext;
@Autowired
private SqlMapper sqlMapper;
@Override
@DataSource(name="master")
@Transactional(rollbackFor=Exception.class)
public void insertMaster() {
System.out.println("------insertMaster--------");
User user = new User("1", "1", "1");
sqlMapper.insertOne(user);
DataSourceServiceImpl dss = applicationContext.getBean(DataSourceServiceImpl.class);
dss.insertSlave();
}
@Override
@DataSource(name="slave")
@Transactional(rollbackFor=Exception.class, propagation=Propagation.REQUIRES_NEW)
public void insertSlave() {
System.out.println("------insertSlave--------");
User user = new User("2", "2", "2");
sqlMapper.insertOne(user);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
六、springboot启动类上开启AOP和Transaction
其中@EnableTransactionManagement事务注解中需要定义order属性,order设置的值要大于AOP类上的order数值,这样@DataSource注解会比@Transactional注解先拦截生效, 从而确保数据源切换在事务开启之前。
@SpringBootApplication
@EnableAspectJAutoProxy
@EnableTransactionManagement(order=Integer.MAX_VALUE-3)
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}