springBoot多数据源配置并配合AOP实现事务代理

1.添加yml配置

这里使用druid数据源配置了密码加密

#主数据源1
      #druid相关配置
    druid:


      ds1:
        #监控统计拦截的filters
        filters: stat,wall,config,logback
        connection-properties: druid.stat.mergeSql=true;druid.stat.logSlowSql=true;config.file=classpath:${spring.profiles.active}/druid.properties;druid.stat.slowSqlMillis=5000;initConnectionSqls=set names utf8mb4;
        #基本属性
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/you_db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: ${password}
        #配置初始化大小/最小/最大
        initial-size: 20
        ##可以配置更多参数


        

      #数据源2
      mall2:
        #监控统计拦截的filters
        filters: stat,wall,config,logback
        connection-properties: druid.stat.mergeSql=true;druid.stat.logSlowSql=true;config.file=classpath:${spring.profiles.active}/druid.properties;druid.stat.slowSqlMillis=5000;initConnectionSqls=set names utf8mb4;
        #基本属性
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/you_db2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: ${password}
        #配置初始化大小/最小/最大
        initial-size: 20
        ##可以配置更多参数
       

Mybatis主数据源配置

注意1:@MapperScan(basePackages=“这里要分包”")
注意2:方法SqlSessionFactory()中getResources(“这里要分包”);

2.添加数据源1的配置类

package com.ym.web.config.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @Author wenbo
 * @Date 2021/2/23 11:39
 * Mybatis主数据源ds1配置
 * 多数据源配置依赖数据源配置
 **/
@Configuration
@MapperScan(basePackages = "com.ym.dao",sqlSessionTemplateRef ="ds1SqlSessionTemplate")
public class MybatisConfigDs1 {


    @Primary
    @Bean(name = "ds1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.ds1")
    public DruidDataSource primaryDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 主数据源 ds1数据源
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Primary
    @Bean("ds1SqlSessionFactory")
    public SqlSessionFactory ds1SqlSessionFactory(@Qualifier("ds1DataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:mapper/**/*.xml"));
        return sqlSessionFactory.getObject();
    }

    /**
     * 事务支持
     * @param dataSource
     * @return
     */
    @Primary
    @Bean(name = "ds1TransactionManager")
    public DataSourceTransactionManager ds1TransactionManager(@Qualifier("ds1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Primary
    @Bean(name = "ds1SqlSessionTemplate")
    public SqlSessionTemplate ds1SqlSessionTemplate(@Qualifier("ds1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

3.添加数据源2的配置类

package com.ym.web.config.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
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;

/**
 * @Author wenbo
 * @Date 2021/2/24 10:15
 * Mybatis主数据源ds2配置
 * 多数据源配置依赖数据源配置
 **/

@Configuration
@MapperScan(basePackages = "com.ym.malldao",sqlSessionTemplateRef ="ds2SqlSessionTemplate")
public class MybatisConfigDs2 {

    @Bean(name = "ds2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.mall2")
    public DruidDataSource primaryDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * ds2数据源
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean("ds2SqlSessionFactory")
    public SqlSessionFactory ds2SqlSessionFactory(@Qualifier("ds2DataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:mallmapper2/**/*.xml"));
        return sqlSessionFactory.getObject();
    }


    /**
     * 事务支持
     * @param dataSource
     * @return
     */
    @Bean(name = "ds2TransactionManager")
    public DataSourceTransactionManager ds2TransactionManager(@Qualifier("ds2DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "ds2SqlSessionTemplate")
    public SqlSessionTemplate ds2SqlSessionTemplate(@Qualifier("ds2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

到这里数据源配置好了,但是重点在于多数据源的事务生效

何为事务?

一般来说,事务是必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

为什么重要?

你去银行存钱,钱存进去了 账户没增加

解决办法:

因为是多数据源,而@Transactional只能使用一次切默认主数据源生效,所以再以切面封装事务
1.设计注解

package com.ym.annotation;

import java.lang.annotation.*;

/**
 * @Author wenbo
 * @Date 2021/3/2 15:02
 **/

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {

    String[] value() default {};
}

2.控制事务类

注意:内部类Db1TxBroker和 Db2TxBroker 必须以注入的方式交给spring

package com.ym.aspect;

import com.ym.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.Callable;
import java.util.stream.Stream;

/**
 * @Author wenbo
 * @Date 2021/3/2 15:05
 **/
@Component
public class ComboTransaction {
    /**
    *数据源配置类中事务控制的名称
    **/
    public static final String DB1_TX = "ds1TransactionManager";

    public static final String DB2_TX = "ds2TransactionManager";

    @Autowired
    private Db1TxBroker db1TxBroker;

    @Autowired
    private Db2TxBroker db2TxBroker;



    public  V inCombinedTx(Callable callable, String[] transactions) {
        if (callable == null) {
            return null;
        }

        Callable combined = Stream.of(transactions)
                .filter(ele -> !StringUtils.isEmpty(ele))
                .distinct()
                .reduce(callable, (r, tx) -> {
                    switch (tx) {
                        case DB1_TX:
                            return () -> db1TxBroker.inTransaction(r);
                        case DB2_TX:
                            return () -> db2TxBroker.inTransaction(r);
                        default:
                            return null;
                    }
                }, (r1, r2) -> r2);

        try {
            return combined.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }



    @Component
    public class Db1TxBroker {

        @Transactional(rollbackFor = {Exception.class,RuntimeException.class},value = DB1_TX)
        public  V inTransaction(Callable callable) {
            try {
                return callable.call();
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Component
    public class Db2TxBroker {

        @Transactional(rollbackFor = {Exception.class,RuntimeException.class},value = DB2_TX)
        public  V inTransaction(Callable callable) {
            try {
                return callable.call();
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

}

3.代码使用

@MultiTransactional 加在方法上这样两个数据源都会生效,当手动抛出Exception和RuntimeExcepion或自定义异常事务都会回滚
以下代码已经测试并运行

/**
     * 小程序积分同步
     * @param phoneNo
     * @param userId
     * @return
     */
    @Override
    @MultiTransactional(value = {ComboTransaction.DB1_TX,ComboTransaction.DB2_TX})
    public BigDecimal extract(String phoneNo, Long userId) {
        ZjhjBdUserModel model = new ZjhjBdUserModel();
        model.setMobile(phoneNo);
        List zjhjBdUser =  baseDao.selectByModel(model);
        BigDecimal currCoin = null;
                if (null == zjhjBdUser || zjhjBdUser.isEmpty()){
            throw new BusinessException(ResultCode.MALL_USER_NOT_EXIST.getCode().toString(),ResultCode.MALL_USER_NOT_EXIST.getMsg());
        }else if (zjhjBdUser.size() > 1){
            throw new BusinessException(ResultCode.MALL_USER_ERROR.getCode().toString(),ResultCode.MALL_USER_ERROR.getMsg());
        }else {
            ZjhjBdUser realUser = zjhjBdUser.get(0);
            Map map = iIntegralConfigSV.userLevelByUserIde(userId);
            currCoin = (BigDecimal) map.get("currCoin");
            if (currCoin.longValue() == 0){
                throw new BusinessException("当前积分为 0,不可提取.");
            }
            //在从数据源操作的代码
            //积分同步到商城
            Integer mallUser = realUser.getId();
            ZjhjBdUserInfoModel infoModel = new ZjhjBdUserInfoModel();
            infoModel.setUserId(mallUser);
            ZjhjBdUserInfo userInfo = iZjhjBdUserInfoSV.selectSingleByModel(infoModel);
            userInfo.setIntegral(userInfo.getIntegral() + currCoin.intValue());
            userInfo.setTotalIntegral(userInfo.getTotalIntegral() + currCoin.intValue());
            iZjhjBdUserInfoSV.updateByIdSelective(userInfo);
            //在主数据源操作的代码
            //小程序 积分变动
            Account account = new Account();
            account.setId((Long) map.get("id"));
            account.setCoinBalance(BigDecimal.ZERO);
            iAccountSV.updateByIdSelective(account);
            
            //发送短信
            SmsUtil.sendSms(phoneNo, SmsTemplateEnum.INTEGRAL_EXTRACT,currCoin.longValue(),"商城");

        }
        return currCoin;
    }

经过实践后发现有些许问题下片讲述 热部署的相关问题

springBoot多数据源配置并配合AOP实现事务代理_第1张图片
如果喜欢我微信扫码关注

end

你可能感兴趣的:(博客笔记,java,spring,后端)