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)。
为什么重要?
你去银行存钱,钱存进去了 账户没增加
因为是多数据源,而@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;
}
经过实践后发现有些许问题下片讲述 热部署的相关问题