springboot 多数据源+多事务配置
工作中使用到了多数据源,网上多为主从模式,但实际工组中需要项目独立的多数据源,没有分布式事务。虽然可以拆分成多个独立的项目,但是考虑到项目成本、调试成本、协作成本等因素,放弃拆分,采用一个项目配置。 以下代码仅供个人记录以及保留解决思路。
说明:
独立模式表示可配置同种类数据库不同版本
使用场景:
- 例1
有以下三个数据库:1)业务数据库 2)业务数据备份库 3)日志、监控数据库(数据量比较大)。 这三个数据库在不进行项目拆分情况下,需要同时配置在一个项目中,一般业务数据库有完整操作权限,另外两个一般只有查询权限,相互之前不需要跨库事务,所以无主数据源且数据库之间相互独立。 - 例2
有以下两个数据库:1)第三方数据库 2)自己的数据库。从第三方数据库同步数据到自己的数据,第三方数据库一般只有查询权限,自己的数据库有完整操作权限,两个数据源一般也不存在跨库事务,所以无主数据源且数据库之间相互独立。
每个数据源一个事务,所以是相互独立,没有主数据源
主从模式一般针对主从数据库使用,网上有很多更加详细的配置说明,以下仅提供核心代码:
注:需要在sevice层的@Transactional注解内增加transactionManager属性,不加则默认使用主事务
Oracle数据源
package com.zygl.cr.multiDatasourceConfig;
import javax.sql.DataSource;
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.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.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@Configuration
@MapperScan(basePackages = {"com.zygl.cr.dao"}, sqlSessionFactoryRef = "oracleSqlSessionFactory")
public class OracleConfig {
@Bean(name = "oracleDataSource")
@ConfigurationProperties(prefix = "spring.datasource.oracle")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "oracleSqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("oracleDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/oracle/**/*.xml");
// 此处可根据需要过滤resources,前提是要与@MapperScan扫描的路径对应上
// ...
bean.setMapperLocations(resources);
return bean.getObject();
}
@Bean(name = "oracleTransactionManager")
// @Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("oracleDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
MySQL数据源
package com.zygl.cr.multiDatasourceConfig;
import javax.sql.DataSource;
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.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.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@Configuration
@MapperScan(basePackages = {"com.zygl.ali.business.dao","com.zygl.workflow.dao","com.zygl.core.dao","com.zygl.admin.dao"}, sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MysqlConfig {
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "mysqlSqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/mysql/**/*.xml");
// 此处可根据需要过滤resources,前提是要与@MapperScan扫描的路径对应上
// ...
bean.setMapperLocations(resources);
return bean.getObject();
}
@Bean(name = "mysqlTransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
配置文件
#数据库连接信息
spring:
datasource:
oracle:
continue-on-error: true
driver-class-name: oracle.jdbc.OracleDriver
jdbc-url: jdbc:oracle:thin:@<host>:<port>/<service_name>
username: 用户名
password: 密码
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 3
maximum-pool-size: 12
auto-commit: true
idle-timeout: 30000
pool-name: oracleHikariCP
max-lifetime: 300000
connection-timeout: 5000
mysql:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://<host>:<port>/<database>?useSSL=false&serverTimezone=PRC&rewriteBatchedStatements=true
username: 用户名
password: 密码
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 3
maximum-pool-size: 12
auto-commit: true
idle-timeout: 30000
pool-name: mysqlHikariCP
max-lifetime: 300000
connection-timeout: 5000
按照以上的修改,其实已经实现多数据源多事务配置,在service层的@Transactional中,增加transactionManager属性,值为DataSourceTransactionManager的Bean的name。
一下两个类的增加,是为了不限于一下两种情况,减少修改:
- 历史项目,需要修改大量包含@Transactional注解的类
- 不想再每一个被@Transactional注解的类增加transactionManager属性
1.继承ProxyTransactionManagementConfiguration类,为了修改原始TransactionInterceptor类
package com.zygl.cr.multiDatasourceConfig;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration;
import org.springframework.transaction.interceptor.TransactionInterceptor;
@Configuration
public class ProxyTransactionManagementConfigurationZygl extends ProxyTransactionManagementConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Override
public TransactionInterceptor transactionInterceptor() {
/*
* 原始代码:
* TransactionInterceptor interceptor = new TransactionInterceptor();
*/
// 自定义类,继承TransactionInterceptor
TransactionInterceptor interceptor = new TransactionInterceptorMulit();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
2.继承TransactionInterceptor类,为了修改原始TransactionInterceptor类
package com.zygl.cr.multiDatasourceConfig;
import java.lang.reflect.Method;
import org.springframework.lang.Nullable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
public class TransactionInterceptorMulit extends TransactionInterceptor {
/** 描述这个变量的作用 */
private static final long serialVersionUID = -85802382483017226L;
@Override
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
/*
* 原始代码:
* final PlatformTransactionManager tm = determineTransactionManager(txAttr);
*/
// 自动以代码,重载determineTransactionManager方法
final PlatformTransactionManager tm = determineTransactionManager(txAttr, targetClass);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback
// calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a
// TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
} catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
} else {
throw new ThrowableHolderException(ex);
}
} else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
} finally {
cleanupTransactionInfo(txInfo);
}
});
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
} catch (ThrowableHolderException ex) {
throw ex.getCause();
} catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
} catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
}
}
/**
* 重写父类的determineTransactionManager方法,最后依然调用父类的方法。
* 当qualifier为空时,调用transactionManager方法,为qualifier赋值。
* 此处有很多情况为判断处理,实际使用时建议根据需要修改
*/
private PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr, Class<?> targetClass) {
if (txAttr == null || getBeanFactory() == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineTransactionManager(txAttr);
} else {
String transactionManager = transactionManager(targetClass);
// transactionManager为null,后续代码将返回默认事务配置
if (transactionManager != null && txAttr instanceof DefaultTransactionAttribute) {
((DefaultTransactionAttribute) txAttr).setQualifier(transactionManager);
}
}
return determineTransactionManager(txAttr);
}
/**
* 获取DataSourceTransactionManager的Bean的name
* 只根据类路径判断,为进行更多情况判断,例如:此类以包含完整的@Transaction注解如何处理;未包含@Transaction是否处理等
* 根据项目实际情况修改获取逻辑
*/
private String transactionManager(Class<?> targetClass) {
String className = targetClass.getName();
if(className.startsWith("oracle的service包路径")) {
return "oracleTransactionManager";
}
if(className.startsWith("mysql的service包路径")) {
return "mysqlTransactionManager";
}
return null;
}
private String methodIdentification(Method method, @Nullable Class<?> targetClass,
@Nullable TransactionAttribute txAttr) {
String methodIdentification = methodIdentification(method, targetClass);
if (methodIdentification == null) {
if (txAttr instanceof DefaultTransactionAttribute) {
methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor();
}
if (methodIdentification == null) {
methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
}
}
return methodIdentification;
}
/**
* Internal holder class for a Throwable in a callback transaction model.
*/
private static class ThrowableHolder {
@Nullable
public Throwable throwable;
}
@SuppressWarnings("serial")
private static class ThrowableHolderException extends RuntimeException {
public ThrowableHolderException(Throwable throwable) {
super(throwable);
}
@Override
public String toString() {
return getCause().toString();
}
}
}
以上为多数据源多事务功能核心代码与逻辑,希望能与有兴趣的朋友多讨论