Dynamic DataSource 多数据源配置【 Springboot + DataSource + MyBatis Plus + Druid】
一、前言
MybatisPlus多数据源配置主要解决的是多数据库连接和切换的问题。在一些大型应用中,由于数据量的增长或者业务模块的增多,可能需要访问多个数据库。这时,就需要配置多个数据源。
二、Springboot + MyBatis Plus 数据源配置
2.1、单数据源配置
2.1.1、引用依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency>
2.1.2、application.yml 配置
spring:datasource:url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
2.1.3、通用配置类
@Configuration
@MapperScan(basePackages = {"com.xx.**.mapper"})
public class MybatisPlusConfig {public MybatisPlusInterceptor mybatisPlusInterceptor(){//新的分页插件配置方法(Mybatis Plus 3.4.0版本及其之后的版本)MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}
2.1.4、使用方式
这里便不过多的说明具体的使用方式了,和正常的MyBatis plus 单库一样
2.2、多数据源配置
2.2.1、引用依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><!-- 集成druid连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.9</version></dependency> <!-- 多数据源所需要使用到的依赖--><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.1.0</version></dependency>
2.2.2、在 application.yml
spring:# 配置数据源信息datasource:dynamic:# 设置默认的数据源或者数据源组,默认值为masterprimary: master# 严格匹配数据源,默认false,true未匹配到指定数据源时抛异常,false使用默认数据源strict: falsedatasource:master:url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8&userSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456slave_1:url: jdbc:mysql://localhost:3306/demo?serverTimezone=GMT%2B8&characterEncoding=utf-8&userSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driverusername: root
2.2.3、通用配置类
@Configuration
@MapperScan(basePackages = {"com.xx.**.mapper"})
public class MybatisPlusConfig {public MybatisPlusInterceptor mybatisPlusInterceptor(){//新的分页插件配置方法(Mybatis Plus 3.4.0版本及其之后的版本)MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}
2.2.4、准备 DataSourceType
public enum DataSourceType {MASTER,SLAVE,THIRD
}
2.2.5、自定义切换数据源的注解 DataSourceType
import java.lang.annotation.*;/*** 定义切换数据源的注解** @author: zhangxiaohu* @date: 2021/6/25 21:59* @Description:*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {/*** 切换数据源名称*/DataSourceType value() default DataSourceType.MASTER;}
2.2.6、准备 DynamicDataSourceContextHolder
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*** 动态数据源名称上下文处理***/
public class DynamicDataSourceContextHolder {public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);/*** 设置数据源* @param key*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 设置数据源* @param key*/public static void setDataSourceType(String dataSourceType) {log.info(" switch to {} data source", dataSourceType);CONTEXT_HOLDER.set(dataSourceType);}/*** 获取数据源名称* @return*/public static String getDataSourceType() {return CONTEXT_HOLDER.get();}/*** 删除当前数据源名称*/public static void clearDataSourceType() {CONTEXT_HOLDER.remove();}}
2.2.7、动态数据源
import com.dq.workflow.context.DynamicDataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 动态数据源***/
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}
2.2.8、在config中配置数据源
import com.dq.workflow.constants.DataSourceConstants;
import org.mybatis.spring.annotation.MapperScan;
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.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;@Component
@Configuration
@PropertySource("classpath:application-test.yml")
@MapperScan({"com.dq.workflow.mapper","com.dq.workflow.activiti.mapper"})
public class DataSourceConfig {/*** 创建数据源db1* @return 数据源db1*/@Bean(name = "masterDataSource")@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}/*** 创建数据源db2* @return 数据源db2*/@Bean(name = "slaveDataSource")@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}/*** 数据源配置* @param db1数据源db1* @param db2数据源db2* @return 动态数据源切换对象。* @Description @Primary赋予该类型bean更高的优先级,使至少有一个bean有资格作为autowire的候选者。*/@Primary@Bean(name = "dynamicDataSource")public DataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("slaveDataSource") DataSource slaveDataSource) {Map<Object, Object> targetDataSourcesMap = new HashMap<>();//主数据源targetDataSourcesMap.put(DataSourceType.MASTER.name(), masterDataSource);//从数据源targetDataSourcesMap.put(DataSourceType.SLAVE.name(), slaveDataSource);// 设置默认数据源、其他数据源return new DynamicDataSource(masterDataSource, targetDataSourcesMap);}
}
2.2.9、在config中配置数据源 文件为 db.properties
这个是将原来 Springboot DB配置单独抽离为一个单独的配置文件 db.properties ,没有这种情况的可以不用看这部分
2.2.9.1、准备 DbProperties
public class DbProperties {public static String masterDriver;public static String masterUrl;public static String masterUserName;public static String masterPassword;public static String masterSchema;public static String masterEncrypted;public static String slaveDriver;public static String slaveUrl;public static String slaveUserName;public static String slavePassword;public static String slaveSchema;public static String slaveEncrypted;}
2.2.9.2、在 application.yml 添加
db:properties:path: C:\\MVNO\\src\\main\\resources\\db.properties
2.2.9.3、修改 DataSourceConfig
@Component
@Configuration
public class DataSourceConfig {@Value("${db.properties.path}")private String path;private PropertyResourceBundle externalProps; @Overridepublic void afterPropertiesSet() throws Exception {InputStream input = null;try {input = Files.newInputStream(Paths.get(path));this.externalProps = new PropertyResourceBundle(input);} catch (Exception ex) {log.error("Load external configuration file failed. path: {}. Cause: {}", path, ex.getMessage());throw new RuntimeException(ex);} finally {IoUtil.close(input);}} public void initDbProperties(String path) {try {DbProperties.masterDriver = this.externalProps.getString("jdbc.mvno.driverClassName");DbProperties.masterUrl = this.externalProps.getString("jdbc.mvno.url");DbProperties.masterUserName = this.externalProps.getString("jdbc.mvno.username");DbProperties.masterPassword = this.externalProps.getString("jdbc.mvno.password");DbProperties.masterSchema = this.externalProps.getString("jdbc.mvno.schema");DbProperties.masterEncrypted = this.externalProps.getString("jdbc.mvno.encrypted");DbProperties.slaveDriver = this.externalProps.getString("jdbc.pbue.driverClassName");DbProperties.slaveUrl = this.externalProps.getString("jdbc.pbue.url");DbProperties.slaveUserName = this.externalProps.getString("jdbc.pbue.username");DbProperties.slavePassword = this.externalProps.getString("jdbc.pbue.password");DbProperties.slaveSchema = this.externalProps.getString("jdbc.pbue.schema");DbProperties.slaveEncrypted = this.externalProps.getString("jdbc.pbue.encrypted");if ("true".equalsIgnoreCase(DbProperties.masterEncrypted)) {DbProperties.masterPassword = Decryption.decrypt(DbProperties.masterPassword);}if ("true".equalsIgnoreCase(DbProperties.slaveEncrypted)) {DbProperties.slavePassword = Decryption.decrypt(DbProperties.slavePassword);}} catch (Exception ex) {log.error("Load db configuration failed. Cause: {}", ex.getMessage());throw new RuntimeException(ex);}}/*** 创建数据源db1* @return 数据源db1*/@Bean(name = "masterDataSource")@Scope(ConfigurableListableBeanFactory.SCOPE_SINGLETON)public DataSource masterDataSource() {initDbProperties(path);DruidDataSource dataSource = DruidDataSourceBuilder.create().build();dataSource.setDriverClassName(DbProperties.masterDriver);dataSource.setUsername(DbProperties.masterUserName);dataSource.setPassword(DbProperties.masterPassword);dataSource.setUrl(DbProperties.masterUrl);dataSource.setMaxWait(60000);dataSource.setMinEvictableIdleTimeMillis(300000);}/*** 创建数据源db2* @return 数据源db2*/@Bean(name = "slaveDataSource")@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")public DataSource slaveDataSource() {initDbProperties(path);DruidDataSource dataSource = DruidDataSourceBuilder.create().build();dataSource.setDriverClassName(DbProperties.slaveDriver);dataSource.setUsername(DbProperties.slaveUserName);dataSource.setPassword(DbProperties.slavePassword);dataSource.setUrl(DbProperties.slaveUrl);dataSource.setMaxWait(60000);dataSource.setMinEvictableIdleTimeMillis(300000);return dataSource;}/*** 数据源配置* @param db1数据源db1* @param db2数据源db2* @return 动态数据源切换对象。* @Description @Primary赋予该类型bean更高的优先级,使至少有一个bean有资格作为autowire的候选者。*/@Primary@Bean(name = "dynamicDataSource")public DataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("slaveDataSource") DataSource slaveDataSource) {Map<Object, Object> targetDataSourcesMap = new HashMap<>();//主数据源targetDataSourcesMap.put(DataSourceType.MASTER.name(), masterDataSource);//从数据源targetDataSourcesMap.put(DataSourceType.SLAVE.name(), slaveDataSource);// 设置默认数据源、其他数据源return new DynamicDataSource(masterDataSource, targetDataSourcesMap);}
}
2.2.9.4、修改 DynamicDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource{public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSourcesMap) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSourcesMap);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}
2.2.10、Aop方式拦截
import cn.xiyou.config.DataSource;
import cn.xiyou.config.DynamicDataSourceContextHolder;
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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Aspect
@Order(1)
@Component
public class DataSourceAspect {private Logger log = LoggerFactory.getLogger(getClass());@Pointcut("@annotation(cn.xiyou.config.DataSource)")public void dsPointCut() {}@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();DataSource dataSource = method.getAnnotation(DataSource.class);if (dataSource != null) {log.info("【before数据源名】" + dataSource.value().name());DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());log.info("【after数据源名】" + dataSource.value().name());}Object obj = null;try {obj = point.proceed();} catch (Throwable throwable) {throwable.printStackTrace();} finally {// 销毁数据源 在执行方法之后DynamicDataSourceContextHolder.clearDataSourceType();}return obj;}}
2.2.11、配置动态表前缀 DynamicTableName
我这边配置这个是因为存在跨账号查询数据库表的情况,这里请根据实际项目情况配置,没有这种需求请跳过
public enum HbueDBEnum {HMVNO_LOOKUP_DP_ZONE_OPER,HBUE_LOOKUP_VPLMN_OPERATOR,HMVNO_LOOKUP_SDR_RATE,HMVNO_LOOKUP_MSP_EVENT_PLAN,HMVNO_LOOKUP_TOPUP_AMOUNT;
}
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import com.mvno.common.db.HbueDBEnum;
import com.mvno.common.domain.DbProperties;import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;public class MonthTableNameHandler implements TableNameHandler {private final List<String> tablelist=new ArrayList<String>(){{for (HbueDBEnum tables : HbueDBEnum.values()) {add(tables.name());}}};@Overridepublic String dynamicTableName(String sql, String tableName) {if(tablelist.contains(tableName)){return DbProperties.slaveSchema + "." + tableName;}else {return DbProperties.masterSchema + "." + tableName;}}}
2.2.12、数据源关闭连接问题
[com.alibaba.druid.util.JdbcUtils:109 ] - close statement error
java.sql.SQLRecoverableException: 关闭的连接
2.2.12.1、application.yml 配置
2.2.12.1.1、druid
这里引入的是专门为spring boot打造的druid-spring-boot-starter,如果引入的是druid则需要配置xml
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>{druid.version}</version></dependency>
druid参数可以不写,会按照默认参数处理,需要指定type
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql:///:3306/数据库名?useSSL=false&useUnicode=true&characterEncoding=UTF-8username:password:type: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 10 # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时min-idle: 10 # 最小连接池数量maxActive: 200 # 最大连接池数量maxWait: 60000 # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置timeBetweenEvictionRunsMillis: 60000 # 关闭空闲连接的检测时间间隔.Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。minEvictableIdleTimeMillis: 300000 # 连接的最小生存时间.连接保持空闲而不被驱逐的最小时间validationQuery: SELECT 1 FROM DUAL # 验证数据库服务可用性的sql.用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUALtestWhileIdle: true # 申请连接时检测空闲时间,根据空闲时间再检测连接是否有效.建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRuntestOnBorrow: false # 申请连接时直接检测连接是否有效.申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。testOnReturn: false # 归还连接时检测连接是否有效.归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。poolPreparedStatements: true # 开启PSCachemaxPoolPreparedStatementPerConnectionSize: 20 #设置PSCache值connectionErrorRetryAttempts: 3 # 连接出错后再尝试连接三次breakAfterAcquireFailure: true # 数据库服务宕机自动重连机制timeBetweenConnectErrorMillis: 300000 # 连接出错后重试时间间隔asyncInit: true # 异步初始化策略remove-abandoned: true # 是否自动回收超时连接remove-abandoned-timeout: 1800 # 超时时间(以秒数为单位)transaction-query-timeout: 6000 # 事务超时时间filters: stat,wall,log4j2useGlobalDataSourceStat: true #合并多个DruidDataSource的监控数据connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 #通过connectProperties属性来打开mergeSql功能;慢SQL记录
2.2.12.1.2、hikari
在spring boot2中引入jdbc的时候如果没有显式指定数据库连接池,默认使用的就是hikari连接池
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
druid一样,可以使用默认参数
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql:///:3306/数据库名?useSSL=false&useUnicode=true&characterEncoding=UTF-8username:password:type: com.zaxxer.hikari.HikariDataSourcehikari:# 连接池中允许的最小连接数。缺省值:10minimum-idle: 10# 连接池中允许的最大连接数。缺省值:10maximum-pool-size: 100# 自动提交auto-commit: true# 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟idle-timeout: 600# 连接池名字pool-name: 泡泡的HikariCP# 一 个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒max-lifetime: 1800000# 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒connection-timeout: 30000
2.2.12.2、在config中配置
initial-size: 10 # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
min-idle: 10 # 最小连接池数量
maxActive: 200 # 最大连接池数量
maxWait: 60000 # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置
timeBetweenEvictionRunsMillis: 60000 # 关闭空闲连接的检测时间间隔.Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
minEvictableIdleTimeMillis: 300000 # 连接的最小生存时间.连接保持空闲而不被驱逐的最小时间
validationQuery: SELECT 1 FROM DUAL # 验证数据库服务可用性的sql.用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
testWhileIdle: true # 申请连接时检测空闲时间,根据空闲时间再检测连接是否有效.建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRun
testOnBorrow: false # 申请连接时直接检测连接是否有效.申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false # 归还连接时检测连接是否有效.归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
poolPreparedStatements: true # 开启PSCache
maxPoolPreparedStatementPerConnectionSize: 20 #设置PSCache值
connectionErrorRetryAttempts: 3 # 连接出错后再尝试连接三次
breakAfterAcquireFailure: true # 数据库服务宕机自动重连机制
timeBetweenConnectErrorMillis: 300000 # 连接出错后重试时间间隔
asyncInit: true # 异步初始化策略
remove-abandoned: true # 是否自动回收超时连接
remove-abandoned-timeout: 1800 # 超时时间(以秒数为单位)
transaction-query-timeout: 6000 # 事务超时时间
filters: stat,wall,log4j2
useGlobalDataSourceStat: true #合并多个DruidDataSource的监控数据
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 #通过connectProperties属性来打开mergeSql功能;慢SQL记录
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();dataSource.setDriverClassName(DbProperties.slaveDriver);dataSource.setUsername(DbProperties.slaveUserName);dataSource.setPassword(DbProperties.slavePassword);dataSource.setUrl(DbProperties.slaveUrl);dataSource.setMaxWait(60000);dataSource.setMinEvictableIdleTimeMillis(300000);
2.2.13、多数据源配置的使用
- 1、使用 @DS 注解切换数据源时,使用springboot数据源的自动配置,需要将DataSourceConfig配置注释掉
- 2、使用 @DataSource 自定义注解时,排除springboot数据源的自动配置,引入DataSourceConfig配置
2.2.13.1、使用master主数据源(不需做任何操作,和之前单数据库一样)
import java.util.List;import cn.xiyou.config.DataSource;
import cn.xiyou.config.DataSourceType;
import cn.xiyou.pojo.Dept;
import org.apache.ibatis.annotations.Param;public interface DeptMapper extends BaseMapper<Dept> {/*** xml 自定义的查询方法* * @return*/// @DataSource(value = DataSourceType.MASTER)List<Dept> findAll();List<Dept> findAllByOrgCode(@Param("orgCode") String orgCode);}
@Service
public interface DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;/*** 使用 xml 自定义的查询方法* * @return*/public List<Dept> selectThreeTreeNode(String faultDate) {return deptMapper.findAll();}/*** 使用 mybatis plus 的查询方法* * @return*/public List<Dept> list(String faultDate) {return deptMapper.list();}}
2.2.13.2、使用slave数据源
import cn.xiyou.config.DataSource;
import cn.xiyou.config.DataSourceType;
import cn.xiyou.pojo.ZTree;import java.util.List;public interface TreeMapper extends BaseMapper<ZTree> {@DataSource(value = DataSourceType.SLAVE)List<ZTree> selectThreeTreeNode();@DataSource(value = DataSourceType.SLAVE)@Select("SELECT COUNT(*) AS total FROM HBUE_LOOKUP_VPLMN_OPERATOR")int findCount();}
@Service
@DS("slave")
public interface TreeServiceImpl implements TreeService {@Autowiredprivate TreeMapper treeMapper;/*** 使用 xml 自定义的查询方法* * @return*/@DataSource(value = DataSourceType.SLAVE)public List<ZTree> selectThreeTreeNode(String faultDate) {return treeMapper.selectThreeTreeNode();}/*** 使用 mybatis plus 的查询方法* * @return*/@DataSource(value = DataSourceType.SLAVE)public List<ZTree> list(String faultDate) {return deptMapper.list();}}
2.2.13.3、实现效果
2.2.13.4、 Q&A
2.2.13.4.1 @Transactional 和 @DS 注解一起使用时, @DS失效的问题
这是因为 @Transactional 开启事务后, 就不会重新拿数据源,因为@DS也得通过切面去获取数据源, 这样就导致了@DS失效.
要解决的话, 就在要切换数据源的方法上也打上@DS, 或者多个数据源有修改操作可以都打上事务注解并改变传播机制,(但这其实是分布式事务的范畴, 这样操作不能保证事务了, plus也提供了
@DSTransactional 来支持, 不够需要借助seata)
2.2.13.4.2 针对@Transactional与@DSTransactional做一个简单的解释。
@Transactional是spring的注解目的就是开启事务,所以在该注解被扫描到的时候就去获取DataSource,此时DynamicDataSourceContextHolder队列中无任务元素,所以获取到的dsKey就是null,之后通过DynamicRoutingDataSource方法中的determinePrimaryDataSource获取主库的DataSource。
@DSTransactional是mp中的注解,该注解下的所有@DS注解在invoke方法中向DynamicDataSourceContextHolder中压入元素,之后在获取determineDataSource的时候或获取到一个dsKey从而选择正确的DataSource。
2.2.13.4.3. 低于V3.3.3版本无法切换数据源的问题
这是因为在V3.3.3版本之前, DruidDataSourceAutoConfigure会比DynamicDataSourceAutoConfiguration先注册, 导致数据库有读取druid的配置, 所以会出现 ,没有druid配置启动报错; 数据源无法切换等等原因, 官方在V3.3.3这个版本修复了, 但是这个版本有个其他的重点bug,官方不让下载, 所以github上找不到这个版本的描述, 只在官网有描述
分两步解决的,
1、去除 druid pom坐标
2、在 DynamicDataSourceAutoConfiguration 上指定优于 DruidDataSourceAutoConfigure加载
解决办法就是排除DruidDataSourceAutoConfigure 类;
2.2.13.4.4、一般使用都是在需要切换数据库在使用中发现经常出现几个小问题,
1、发现配置多数据源之后批量执行的方法都走的主库。
2、针对相同库的事务如何处理。
3、针对不同库的事务如何处理。
在使用多数据源的时候可以开启debug日志查看在每次创建SqlSession之后选用的是哪个数据源。例如:
- 1、针对问题1,在使用mp的时候可能执行一些批量的方法,比如saveBatch等,进入saveBatch方法中可以看到在这个方法上默认有@Transactional注解,对于该注解默认使用的都是主库,所以对于从库的一些操作因为无法切换从库产生一些问题。解决方法:
针对问题1的:同一个源的批量操作,可以在外部方法使用@DS(“bbb”)优先指定需要操作的源。
针对问题1的:不同源的批量操作,可以考虑自己实现批量操作等。
-
2、针对问题2,针对相同库的事务,可以使用@DS与@Transactional搭配实现
-
3、针对问题3,这对不同库的事务,可以使用@DSTransactional实现
总结一些:对于mp的多数据源的事务或者查询问题首先看每次sql执行时选择的源,另外使用@DS与@Transactional与@DSTransactional三个注解基本可以解决遇到的事务失效等问题。
针对三个注解给一个描述语:
@DS:就是用来切换数据源
@Transactional:默认只针对主库
@DSTransactional:可以理解为在事务中可以切换数据源
相关文章:

Dynamic DataSource 多数据源配置【 Springboot + DataSource + MyBatis Plus + Druid】
一、前言 MybatisPlus多数据源配置主要解决的是多数据库连接和切换的问题。在一些大型应用中,由于数据量的增长或者业务模块的增多,可能需要访问多个数据库。这时,就需要配置多个数据源。 二、Springboot MyBatis Plus 数据源配置 2.1、单数…...

MyBatis:配置文件
MyBatis 前言全局配置文件映射配置文件注 前言 在 MyBatis 中,配置文件分为 全局配置文件(核心配置文件) 和 映射配置文件 。通过这两个配置文件,MyBatis 可以根据需要动态地生成 SQL 语句并执行,同时将结果集转换成 …...
ARM,基础、寄存器
1.认识ARM 1)是一家公司 2)做RISC处理器内核 3)不生产芯片 2.ARM处理器的最新发展(重要) 高端产品线: cortex-A9 主要做音视频开发,例如:手机 平板..... 中端产品线:cortex-R 主要做实时性要求比较高的系统 例如&#…...

FC-TSGAS-1624 CP451-10 MVI56E-MNETC IC697CMM742
FC-TSGAS-1624 CP451-10 MVI56E-MNETC IC697CMM742. Variscite的DART-MX8M-PLUS和VAR-SOM-MX8M-PLUS基于恩智浦i.MX 8M Plus SoC,集成人工智能能力高达每秒2.3万亿次运算(TOPS)。这些产品,结合海螺-8 AI处理器提供多达26个top,显著优于市场…...

异或运算.
相同为0,不同为1。 1 ^ 10 0 ^ 00 1 ^ 01 0 ^ 11性质: 0 ^ N N N ^ N 0交换、结合 a ^ b b ^ a; (a ^ b) ^ c a ^ (b ^ c); 因此异或全部的元素的结果就是那个只出现1次的元素。 实现两个值的交换,而不必使…...

NewStarCTF2023week4-逃(反序列化字符串逃逸)
打开链接,大致审一下php代码,是反序列化相关的; 结合题目提示,很典型的字符串逃逸; 并且属于替换修改后导致序列化字符串变长的类型; 看似加了一个waf函数对我们提交的内容进行了过滤替换,实…...
PyTorch Tensor 形状
查看张量形状 有两种方法查看张量形状: 通过属性查看 Tensor.shape通过方法查看 Tensor.size() 两种方式的结果都是一个 torch.Size 类型(元组的子类)的对象 >>> t torch.empty(3, 4) >>> t.size() torch.Size([3, 4]) # 获取 dim1 维度的 size >>…...

RabbitMQ运行机制和通讯过程介绍
文章目录 1.RabbitMQ 环境搭建2.RabbitMQ简介3.RabbitMQ的优势:4. rabbitmq服务介绍4.1 rabbitmq关键词说明4.2 消息队列运行机制4.3 exchange类型 5.wireshark抓包查看RabbitMQ通讯过程 1.RabbitMQ 环境搭建 参考我的另一篇:RabbitMQ安装及使用教程&am…...
UE4 TextRender显示中文方法
UE4 TextRender显示中文 1.内容浏览器右键,用户界面->字体。新建一个。 2.添加字体,右边栏,细节。字体缓存类型:离线。 3.高度参数就是字体大小,导入选项勾选”仅透明度”,字符里输入字库的字符。 4.资产,重新导…...

C++动态规划算法的应用:得到 K 个半回文串的最少修改次数 原理源码测试用例
本文涉及的基础知识点 动态规划 题目 得到 K 个半回文串的最少修改次数 给你一个字符串 s 和一个整数 k ,请你将 s 分成 k 个 子字符串 ,使得每个 子字符串 变成 半回文串 需要修改的字符数目最少。 请你返回一个整数,表示需要修改的 最少…...

Pyside6 QFileDialog
Pyside6 QFileDialog Pyside6 QFileDialog常用函数getOpenFileNamegetOpenFileNamesgetExistingDirectorygetSaveFileName 程序界面程序主程序 Pyside6 QFileDialog提供了一个允许用户选择文件或目录的对话框。关于QFileDialog的使用可以参考下面的文档 https://doc.qt.io/qtfo…...
Leetcode1793. Maximum Score of a Good Subarray
给定一个数组和一个下标 k k k 子数组 ( i , j ) (i,j) (i,j)分数定义为 min ( n u m s [ i ] , n u m s [ i 1 ] , ⋯ , n u m s [ j ] ) ∗ ( j − i 1 ) \min\left(nums[i], nums[i 1],\cdots, nums[j]\right)*\left(j-i1\right) min(nums[i],nums[i1],⋯,nums[j])∗(…...

只需五步,在Linux安装chrome及chromedriver(CentOS)
一、安装Chrome 1)先执行命令下载chrome: wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm2)安装chrome yum localinstall google-chrome-stable_current_x86_64.rpm看到下图中的Complete出现则代表安装…...

第01章-Java语言概述
目录 1 常见DOS命令 常用指令 相对路径与绝对路径 2 转义字符 3 安装JDK与配置环境变量 JDK与JRE JDK的版本 JDK的下载 JDK的安装 配置path环境变量 4 Java程序的编写与执行 5 Java注释 6 Java API文档 7 Java核心机制:JVM 1 常见DOS命令 DOS(…...

Spring | Spring Cache 缓存框架
Spring Cache 缓存框架: Spring Cache功能介绍Spring Cache的Maven依赖Spring Cache的常用注解EnableCaching注解CachePut注解Cacheable注解CacheEvict注解 Spring Cache功能介绍 Spring Cache是Spring的一个框架,实现了基于注解的缓存功能。只需简单加一…...

雷达开发的基本概念fft,cfar,以及Clutter, CFAR,AoA
CFAR Constant False-Alarm Rate的缩写。在雷达信号检测中,当外界干扰强度变化时,雷达能自动调整其灵敏度,使雷达的虚警概率保持不变。具有这种特性的接收机称为恒虚警接收机。雷达信号的检测总是在干扰背景下进行的,这些干扰包括…...

什么是大数据测试?有哪些类型?应该怎么测?
随着目前世界上各个国家使用大数据应用程序或应用大数据技术场景的数量呈指数增长,相应的,对于测试大数据应用时所需的知识与大数据测试工程师的需求也在同步增加。 针对大数据测试的相关技术已慢慢成为当下软件测试人员需要了解和掌握的一门通用技术。…...
03-垃圾收集策略与算法
垃圾收集策略与算法 程序计数器、虚拟机栈、本地方法栈随线程而生,也随线程而灭;栈帧随着方法的开始而入栈,随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性,在这几个区域内不需要过多考虑回收的问题,因…...
1.AUTOSAR的架构及方法论
在15、16年之前,AUTOSAR这个东西其实是被国内很多大的OEM或者供应商所排斥的。为什么?最主要的原因还是以前采用手写底层代码+应用层模型生成代码的方式进行开发。每个供应商或者OEM都有自己的软件规范或者技术壁垒,现在提个AUTOSAR想搞统一,用一个规范来收割汽车软件供应链…...
Kotlin中的List集合
在Kotlin中,List集合用于存储一组有序的元素。List集合分为可变集合(MutableList)和不可变集合(List)。本篇博客将分别介绍可变集合和不可变集合,并提供相关的API示例代码。 不可变集合(List&a…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...