springboot多数据源多事务配置

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();
		}
	}
}

以上为多数据源多事务功能核心代码与逻辑,希望能与有兴趣的朋友多讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值