springboot 事务统一配置_springboot基于注解动态配置多数据源以及多数据源的事务统一...

一、动态注入多数据源

1、配置多数据源配置文件(application-db.properties)

######多数据源配置文件####################

###第一个####

spring.datasource.first.name=first

spring.datasource.first.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl

spring.datasource.first.username=sepcore

spring.datasource.first.password=sepcore

spring.datasource.first.driverClassName=oracle.jdbc.driver.OracleDriver

spring.datasource.first.mapperLocations=classpath:mappers/*Mapper.xml

####第二个####

spring.datasource.second.name=second

spring.datasource.second.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false

spring.datasource.second.username=root

spring.datasource.second.password=123456

spring.datasource.second.driverClassName=com.mysql.jdbc.Driver

spring.datasource.second.mapperLocations=classpath:mappers/*Mapper.xml

#####mapper接口所在包#######

scanner.mapperInterfacePackage=com.example.demo.mappers

2、读取配置文件类(DataSourceConfig)

public classDataSourceConfig {/*** 存储dataSource、SqlSessionTemplate、DataSourceTransactionManager*/

private Map>mapMap;/*** 获取mybatis扫描的指定接口包(所有数据源的接口放在同一的父包下面)*/

privateString mapperInterfacePackage;publicDataSourceConfig(){

mapMap= new HashMap<>();

InputStream in= DataSourceConfig.class.getClassLoader().

getResourceAsStream("application-db.properties");

Properties properties= newProperties();try{

properties.load(in);

}catch(IOException e) {

e.printStackTrace();

}

Set set =properties.stringPropertyNames();for(String s : set) {//判断是否是mapper接口指定包路径

if (s.contains("mapperInterfacePackage")){

mapperInterfacePackage=properties.get(s).toString();continue;

}

String key= s.substring(0, s.lastIndexOf("."));if(mapMap.containsKey(key)){

Map map =mapMap.get(key);

map.put(s,properties.get(s));

}else{

Mapmap = new HashMap<>();

map.put(s,properties.get(s));

mapMap.put(key,map);

}

}

}publicString getMapperInterfacePackage() {returnmapperInterfacePackage;

}/*** 获取SqlSessionTemplate

*@return*@throwsException*/

public MapgetSqlSessionTemplateAndDataSource() throwsException {

Set>> entries = this.mapMap.entrySet();

Mapresult = new HashMap<>(entries.size());for (Map.Entry>entry : entries) {

String key=entry.getKey();

Map map =entry.getValue();

DataSource dataSource= DataSourceBuilder.create().url(map.get(key+".url").toString()).

username(map.get(key+".username").toString()).password(map.get(key+".password").toString()).

driverClassName(map.get(key+".driverClassName").toString()).

build();//为每个数据源设置事务

DataSourceTransactionManager dataSourceTransactionManager = newDataSourceTransactionManager();

dataSourceTransactionManager.setDataSource(dataSource);

SqlSessionFactoryBean sqlSessionFactoryBean= newSqlSessionFactoryBean();//设置dataSource数据源

sqlSessionFactoryBean.setDataSource(dataSource);//设置*mapper.xml路径

sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(map.get(key+".mapperLocations").toString()));

String s= map.get(key + ".name").toString();

result.put(s+"SqlSessionTemplate",newSqlSessionTemplate(sqlSessionFactoryBean.getObject()));

result.put(s+"DataSource",dataSource);

result.put(s+"DataSourceTransactionManager",dataSourceTransactionManager);

}returnresult;

}

}

3、使用注解(DataSourceRoute),确定每个mapper接口使用哪个数据源

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)public @interfaceDataSourceRoute {

String name()default "first";

}

@DataSourceRoutepublic interfaceSepUserMapper {

List>findByUserLevel(Long userLevel);void insert(Mapmap);

}

@DataSourceRoute(name = "second")public interfaceIDiDataItemMapper {

@Select("SELECT dataitem_id,name FROM di_dataitem WHERE dataitem_id=#{dataItemId}")

MapselectOne(Long dataItemId);void insert(Mapmap);

}

4、扫描指定包下面的mapper接口

public classClassScanner {public static Map>getMapperInterface(String mapperInterfacePackage) throwsException {

Map>classMap = new HashMap<>();

ClassLoader loader=Thread.currentThread().getContextClassLoader();//将"."替换成"/"

String packagePath = mapperInterfacePackage.replace(".", "/");

URL url=loader.getResource(packagePath);

List fileNames = null;if (url != null) {

String type=url.getProtocol();if ("file".equals(type)) {

fileNames= getClassNameByFile(url.getPath(), null, true);

}

}for(String classPath : fileNames) {

classMap.putAll(getClassByPath(classPath));

}returnclassMap;

}/*** 读取package下的所有类文件

*@paramfilePath

*@paramclassName

*@paramchildPackage

*@return

*/

private static List getClassNameByFile(String filePath, List className, booleanchildPackage) {

List myClassName = new ArrayList<>();

File file= newFile(filePath);

File[] childFiles=file.listFiles();for(File childFile : childFiles) {if(childFile.isDirectory()) {if(childPackage) {

myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));

}

}else{

String childFilePath=childFile.getPath();if (childFilePath.endsWith(".class")) {

childFilePath= childFilePath.substring(childFilePath.indexOf("\\classes") + 9,

childFilePath.lastIndexOf("."));

childFilePath= childFilePath.replace("\\", ".");

myClassName.add(childFilePath);

}

}

}returnmyClassName;

}/*** 将Mapper的标准文件,转成 Mapper Class

*@paramclassPath

*@return*@throwsException*/

private static Map>getClassByPath(String classPath)throwsException{

ClassLoader loader=Thread.currentThread().getContextClassLoader();

Map> classMap = new HashMap<>();

classMap.put(getClassAlias(classPath),loader.loadClass(getFullClassName(classPath)));returnclassMap;

}/*** 将Mapper的标准文件,转成java标准的类名称

*@paramclassPath

*@return*@throwsException*/

private staticString getFullClassName(String classPath)throwsException{int comIndex = classPath.indexOf("com");

classPath=classPath.substring(comIndex);

classPath= classPath.replaceAll("\\/", ".");returnclassPath;

}/*** 根据类地址,获取类的Alais,即根据名称,按照驼峰规则,生成可作为变量的名称

*@paramclassPath

*@return*@throwsException*/

private staticString getClassAlias(String classPath)throwsException{

String split= "\\/";

String[] classTmp=classPath.split(split);

String className= classTmp[classTmp.length-1];returntoLowerFisrtChar(className);

}/*** 将字符串的第一个字母转小写

*@paramclassName

*@return

*/

private staticString toLowerFisrtChar(String className){

String fisrtChar= className.substring(0,1);

fisrtChar=fisrtChar.toLowerCase();return fisrtChar+className.substring(1);

}

5、使用BeanFactoryPostProcessor动态插入数据源

@Componentpublic class DataSourceBean implementsBeanFactoryPostProcessor{

@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throwsBeansException {

System.out.println("在spring处理bean前,将自定义的bean注册到容器中======================"); DataSourceConfig dataSourceConfig = newDataSourceConfig();try{

Map sqlSessionTemplateAndDataSource =dataSourceConfig.getSqlSessionTemplateAndDataSource();

Map> mapperInterface =ClassScanner.getMapperInterface(dataSourceConfig.getMapperInterfacePackage());

Set>> entries =mapperInterface.entrySet();for (Map.Entry>entry : entries) {

MapperFactoryBean mapperFactoryBean= newMapperFactoryBean();

Class> value =entry.getValue();

DataSourceRoute dataSourceRoute= value.getAnnotation(DataSourceRoute.class);if (null==dataSourceConfig){continue;

}

String name=dataSourceRoute.name();

SqlSessionTemplate template= (SqlSessionTemplate) sqlSessionTemplateAndDataSource.get(name + "SqlSessionTemplate");

mapperFactoryBean.setMapperInterface(value);

mapperFactoryBean.setSqlSessionTemplate(template);

mapperFactoryBean.afterPropertiesSet();

configurableListableBeanFactory.registerSingleton(name+"MapperFactory",mapperFactoryBean.getObject());

configurableListableBeanFactory.registerSingleton(name+"DataSource",sqlSessionTemplateAndDataSource.get(name + "DataSource"));

configurableListableBeanFactory.registerSingleton(name+"SqlSessionTemplate",template);

configurableListableBeanFactory.registerSingleton(name+"DataSourceTransactionManager",sqlSessionTemplateAndDataSource.get(name+"DataSourceTransactionManager"));

}

}catch(Exception e) {

System.err.println(e.getMessage());

}

}

}

至此多数据源动态加载就完成了。

二、多数据源统一事务控制

当使用多数据源时,单一的事务会出现问题(当在service层同时操作两个数据源时,当发生异常,只会回滚离抛出异常最近的数据源的数据)

1、自定义事务注解

@Target({ElementType.METHOD,ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)public @interfaceCustomTransaction {

String[] name()default {"firstDataSourceTransactionManager"};

}

2、创建aop切面进行事务控制

@Component

@Aspectpublic classTransactionAop {

@Pointcut(value= "@annotation(com.example.demo.annon.CustomTransaction)")public voidpointCut(){}

@Around(value= "pointCut()&&@annotation(annotation)")public Object twiceAsOld(ProceedingJoinPoint point, CustomTransaction annotation) throwsThrowable {

Stack dataSourceTransactionManagerStack = new Stack();

Stack transactionStatuStack = new Stack();try{if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {return null;

}

Object ret=point.proceed();

commit(dataSourceTransactionManagerStack, transactionStatuStack);returnret;

}catch(Throwable e) {

rollback(dataSourceTransactionManagerStack, transactionStatuStack);throwe;

}

}/*** 开启事务处理方法

*

*@paramdataSourceTransactionManagerStack

*@paramtransactionStatuStack

*@parammultiTransactional

*@return

*/

private boolean openTransaction(StackdataSourceTransactionManagerStack,

StacktransactionStatuStack, CustomTransaction multiTransactional) {

String[] transactionMangerNames=multiTransactional.name();if(ArrayUtils.isEmpty(multiTransactional.name())) {return false;

}for(String beanName : transactionMangerNames) {//根据事务名称获取具体的事务

DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil

.getBean(beanName);

TransactionStatus transactionStatus=dataSourceTransactionManager

.getTransaction(newDefaultTransactionDefinition());

transactionStatuStack.push(transactionStatus);

dataSourceTransactionManagerStack.push(dataSourceTransactionManager);

}return true;

}/*** 提交处理方法

*

*@paramdataSourceTransactionManagerStack

*@paramtransactionStatuStack*/

private void commit(StackdataSourceTransactionManagerStack,

StacktransactionStatuStack) {while (!dataSourceTransactionManagerStack.isEmpty()) {

dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop());

}

}/*** 回滚处理方法

*

*@paramdataSourceTransactionManagerStack

*@paramtransactionStatuStack*/

private void rollback(StackdataSourceTransactionManagerStack,

StacktransactionStatuStack) {while (!dataSourceTransactionManagerStack.isEmpty()) {

dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());

}

}

}

3、在service层指定使用哪个事务

//注意事务的命名规则

@CustomTransaction(name = {"firstDataSourceTransactionManager","secondDataSourceTransactionManager"})public voidsetSepUserMapper(){//操作数据源2

Mapmm = new HashMap<>(2);

mm.put("dataitemId",1L);

mm.put("name","测试");

diDataItemMapper.insert(mm);//操作数据源1

Mapmap = new HashMap<>(3);

map.put("userId",1L);

map.put("userName","张三");

map.put("name","平台管理员");

sepUserMapper.insert(map);throw new RuntimeException("sfsa");

}

辅助类:SpringContextUtil

@Componentpublic class SpringContextUtil implementsApplicationContextAware{private staticApplicationContext applicationContext;

@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {

SpringContextUtil.applicationContext=applicationContext;

}/*** @Description: 获取spring容器中的bean,通过bean名称获取

*@parambeanName bean名称

*@return: Object 返回Object,需要做强制类型转换

*@author: zongf

* @time: 2018-12-26 10:45:07*/

public staticObject getBean(String beanName){returnapplicationContext.getBean(beanName);

}/*** @Description: 获取spring容器中的bean, 通过bean类型获取

*@parambeanClass bean 类型

*@return: T 返回指定类型的bean实例

*@author: zongf

* @time: 2018-12-26 10:46:31*/

public static T getBean(ClassbeanClass) {returnapplicationContext.getBean(beanClass);

}/*** @Description: 获取spring容器中的bean, 通过bean名称和bean类型精确获取

*@parambeanName bean 名称

*@parambeanClass bean 类型

*@return: T 返回指定类型的bean实例

*@author: zongf

* @time: 2018-12-26 10:47:45*/

public static T getBean(String beanName, ClassbeanClass){returnapplicationContext.getBean(beanName,beanClass);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 中,配置多数据源事务可以通过使用 `@Transactional` 注解和 `TransactionManager` 来实现。 首先,需要在配置文件中定义多个数据源,并为每个数据源配置一个事务管理器。例如: ```yaml spring: datasource: primary: url: jdbc:mysql://localhost:3306/db1 username: user1 password: password1 secondary: url: jdbc:mysql://localhost:3306/db2 username: user2 password: password2 jpa: database-platform: org.hibernate.dialect.MySQL5Dialect hibernate: ddl-auto: update # 配置事务管理器 primaryTransactionManager: type: JpaTransactionManager entityManagerFactory: primaryEntityManagerFactory secondaryTransactionManager: type: JpaTransactionManager entityManagerFactory: secondaryEntityManagerFactory ``` 然后,在使用 `@Transactional` 注解时,需要指定要使用的事务管理器的名称。例如: ```java @Service public class MyService { @Autowired @Qualifier("primaryTransactionManager") private PlatformTransactionManager primaryTransactionManager; @Autowired @Qualifier("secondaryTransactionManager") private PlatformTransactionManager secondaryTransactionManager; @Transactional("primaryTransactionManager") public void doSomethingInPrimaryDataSource() { // ... } @Transactional("secondaryTransactionManager") public void doSomethingInSecondaryDataSource() { // ... } } ``` 在上面的示例中,`MyService` 类中定义了两个方法,分别使用了不同的事务管理器。使用 `@Transactional` 注解时,通过指定事务管理器的名称来决定使用哪个数据源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值