多数据源事物管理(事物+AOP)

1.问题说明

2.思路

        有两个数据源,因此就有两个事物管理器,我们可以在需要开启事物的方法内手动控制事物的提交。

        使用AOP环绕通知+自定义注解,在执行目标方法之前传入需要用到的两个或者多个事务管理器的名称,在切面内通过名称获取对应管理器的Bean。然后执行目标方法,若发生异常,手动回滚。因此需要重新编写回滚和提交的方法。

3.实现步骤

1.准备工具类:根据名称获取对应的Bean

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
​
@Component
@Lazy(false)
public class SpringContextUtil implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(SpringContextUtil.class);
    private static ApplicationContext applicationContext;
​
    public SpringContextUtil() {
    }
​
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextUtil.applicationContext == null) {
            SpringContextUtil.applicationContext = applicationContext;
        }
    }
​
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
​
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }
​
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }
​
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

2.创建自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface MoreTransaction {
    String[] value() default {};
}

3.配置事物管理器和数据源

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.app.spring.SpringAppEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
​
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
​
/**
 * 一共有两个配置类,一个是ProcessEngineConfiguration,一个是SpringAppEngineConfiguration
 * 他们里面都需要重写configure方法来进行配置
 * 配置数据源应该在SpringAppEngineConfiguration中设定
 * 前者是配置ProcessEngine的,如自动生成表,设置中文,在yml文件中配置的属性便是在此类中读取
 */
@Configuration
@PropertySource("classpath:application-dev.yml")
@Slf4j
@EnableTransactionManagement
public class FlowableConfig implements EngineConfigurationConfigurer<SpringAppEngineConfiguration> {
​
    //读取配置
    @Value("${flow.username}")
    private String user;
    @Value("${flow.password}")
    String password;
    @Value("${flow.url}")
    String jdbcUrl;
    @Value("${flow.driver-class-name}")
    String driverClass;
    @Value("${flow.initialPoolSize}")
    Integer initialPoolSize;
    @Value("${flow.maxPoolSize}")
    int maxPoolSize;
​
    //配置数据源
    @Bean
    public DruidDataSource flowDataSource() throws PropertyVetoException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl(jdbcUrl);
        dataSource.setDriverClassName(driverClass);
        dataSource.setInitialSize(initialPoolSize);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolSize);
        return dataSource;
    }
​
    /**
     * @return
     * @throws PropertyVetoException
     */
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DruidDataSource dataSource() throws PropertyVetoException {
        return new DruidDataSource();
    }
​
    //配置第一个数据源的事务管理器,定义bean名称为 transactionManagerOne
    @Primary
    @Bean(name = "transactionManagerOne")
    public PlatformTransactionManager transactionManagerOne(@Qualifier("dataSource") DataSource dataSourceOne) {
        return new DataSourceTransactionManager(dataSourceOne);
    }
​
    //flowable 事物管理器
    @Bean(name = "transactionManagerTwo")
    public PlatformTransactionManager transactionManagerTwo() throws PropertyVetoException {
        return new DataSourceTransactionManager(flowDataSource());
    }
​
    @Override
    public void configure(SpringAppEngineConfiguration engineConfiguration) {
        try {
            //把数据源设置进来
            engineConfiguration.setDataSource(flowDataSource());
            //配置对应的事务管理器
            engineConfiguration.setTransactionManager(transactionManagerTwo());
            log.info("配置flowable数据源成功");
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }
}
​

        我们可以看到,定义了两个数据源,默认数据源需要使用@Primary注解表示默认数据源,flowbale使用的数据源是flowDataSource()方法提供。然后需要对这两个数据源分别指定事务管理器,一个transactionManagerOne,一个transactionManagerTwo,在管理器里面分别指定数据源。

4.定义切面TransactionAspect


import com.igeek.server.annotation.MoreTransaction;
import com.igeek.utils.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
​
import java.util.Stack;
​

@Aspect
@Component
@Slf4j
public class TransactionAspect {
    //切入点  返回值 包.类.方法(参数)  ..表示参数
    //指定加了注解的才拦截
    @Pointcut("execution(* com.igeek.server.service.serviceImpl.*.*(..)) && @annotation(com.igeek.server.annotation.MoreTransaction)")
    public void moreTransaction() {
    }
​
    @Around("moreTransaction()&&@annotation(annotation)")
    public Object twoTransaction(ProceedingJoinPoint thisJoinPoint, MoreTransaction annotation) throws Throwable {
        //存放事务管理器的栈
        Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<>();
        //存放事务状态
        Stack<TransactionStatus> transactionStatusStack = new Stack<>();
        try{
            // 检查是否有需要开启事务的事务管理器名字
            if(!openTransaction(dataSourceTransactionManagerStack,transactionStatusStack,annotation))
                return null;
            Object proceed= thisJoinPoint.proceed();
            //如果没有异常,说明两个sql都执行成功,两个数据源的sql全部提交事务
            commit(dataSourceTransactionManagerStack, transactionStatusStack);
            return proceed;
        }catch (Throwable e){
        //业务代码发生异常,回滚两个数据源的事务
            rollback(dataSourceTransactionManagerStack, transactionStatusStack);
            log.error(String.format("MultiTransactionalAspect, method:%s-%s occors error:",
                    thisJoinPoint.getTarget().getClass().getSimpleName(), thisJoinPoint.getSignature().getName()), e);
            throw e;
        }
    }
​
    private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatusStack, MoreTransaction annotation) {
        // 获取需要开启事务的事务管理器名字
        String[] transactionMangerNames = annotation.value();
        // 检查是否有需要开启事务的事务管理器名字
        if (ArrayUtils.isEmpty(annotation.value())) {
            return false;
        }
        // 遍历事务管理器名字数组,逐个开启事务并将事务状态和管理器存入栈中
        for (String beanName : transactionMangerNames) {
            // 从Spring上下文中获取事务管理器
            DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil.getBean(beanName);
            // 创建新的事务状态
            TransactionStatus transactionStatus = dataSourceTransactionManager
                    .getTransaction(new DefaultTransactionDefinition());
            // 将事务状态和事务管理器存入对应的栈中
            transactionStatusStack.push(transactionStatus);
            dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
        }
        return true;
    }
​
    private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                        Stack<TransactionStatus> transactionStatuStack) {
        // 循环,直到事务管理器栈为空
        while (!dataSourceTransactionManagerStack.isEmpty()) {
            // 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
            // 提交当前事务状态
            dataSourceTransactionManagerStack.pop()
                    .commit(transactionStatuStack.pop());
        }
    }
​
    private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                          Stack<TransactionStatus> transactionStatuStack) {
        // 循环,直到事务管理器栈为空
        while (!dataSourceTransactionManagerStack.isEmpty()) {
            // 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
            // 回滚当前事务状态
            dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
        }
    }
​
}

指定切点后,分别写了四个方法:

  1. twoTransaction()为环绕通知。

  2. openTransaction()为开启事物,将读取注解的内容,也就是事物管理器的名字,在方法内获取两个或多个事务管理器放到栈中。

  3. commit()事物提交方法。

  4. rollback()事物回滚方法。

5.使用

在方法上指定事物管理器名称即可。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP(面向切面编程)是Spring框架中的一个组件,它允许您以一种声明性的方式来处理横切关注点(如事务管理,日志记录等)。 通过使用AOP,可以将这些关注点从应用程序的主体中分离出来,从而实现代码的复用和灵活性。 在使用Spring框架中实现多数据源的切换时,可以使用自定义注解的形式来实现。首先,首先在应用程序的主体中定义两个数据源。 然后,可以定义一个自定义注解,用于标识哪些方法应该使用哪个数据源。例如,使用“@Primary”注解标记主要数据源,使用“@Secondary”注解标记辅助数据源。 然后,在Spring配置中定义一个AOP切面,该切面使用上述自定义注解来切换数据源。下面是这种方法的一个示例: ```java @Aspect @Component public class DataSourceAspect { @Around("@annotation(Primary)") public Object primaryDataSource(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 切换到主要数据源 DynamicDataSourceContextHolder.setDataSource(DynamicDataSourceContextHolder.DATA_SOURCE_PRIMARY); try { return proceedingJoinPoint.proceed(); } finally { // 切换回默认数据源 DynamicDataSourceContextHolder.clearDataSource(); } } @Around("@annotation(Secondary)") public Object secondaryDataSource(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 切换到辅助数据源 DynamicDataSourceContextHolder.setDataSource(DynamicDataSourceContextHolder.DATA_SOURCE_SECONDARY); try { return proceedingJoinPoint.proceed(); } finally { // 切换回默认数据源 DynamicDataSourceContextHolder.clearDataSource(); } } } ``` 在上面的代码中,我们可以看到“@Around”注解被用于定义一个环绕通知,该通知基于使用“@Primary”或“@Secondary”注解的方法进行拦截。 在方法执行之前,我们使用“DynamicDataSourceContextHolder”来将数据源设置为主要或辅助数据源。 在方法执行完成之后,我们将数据源切换回默认数据源。 最后,我们可以将“@Primary”和“@Secondary”注解带到相应的方法上,以切换不同的数据源,例如: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @Primary public User getUserById(long id) { return userDao.getUserById(id); } @Override @Secondary public List<User> getAllUsers() { return userDao.getAllUsers(); } } ``` 在上面的代码中,我们可以看到“@Primary”注解被用于getUserById()方法,表示这个方法应该从主要数据源中读取数据。相反,getAllUsers()方法被标记为“@Secondary”注解,表示这个方法应该从辅助数据源中读取数据。 通过这种方式,我们可以很容易地切换应用程序中的不同数据源,并且代码的重复率很低。这种方法适用于需要在应用程序的不同部分使用不同数据源的多租户应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值