SpringBoot如何实现手动初始化多数据源、开启事务

赵牧野 2019-07-26 06:25:10
本人想实现一个springboot连接多数据源、并且各个数据源能开启事务的功能,网上搜索了https://www.jianshu.com/p/aa3c383d0ca9,以这个链接为代表,都是基于注解的。特点是这些数据源虽然是多个,但是是确定的,比如5个就是5个,6个就是6个,可以用注解一一配置,也意味着如果加新的数据源,需要动配置文件、和java代码。
我想做的效果:这些数据源配置信息想放在表里,而且可能随时添加新的配置信息。想做到添加进去配置,不动java代码就能使用该数据源。自己写了一部分,在开启事务那里卡住了,不知道大家有思路吗?


package com.zhaojufei.dataclean.common.dao;

import com.alibaba.druid.pool.DruidDataSource;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zhaojufei.dataclean.common.dao.entity.DataSourceConfig;
import com.zhaojufei.dataclean.common.dao.entity.MybatiesConfig;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;

@Slf4j
@Component
public class DataSourceHolder {

/**
* 数据源Map
*/
public static Map<String, SqlSessionTemplate> sqlSessionTemplateMap = Maps.newHashMap();

/**
* 获取key对应的数据源操作客户端SqlSessionTemplate
*/
public static SqlSessionTemplate getSqlSessionTemplate(String key) {
return sqlSessionTemplateMap.get(key);
}

/**
* 创建数据源
*/
@PostConstruct
public void init() {

// 这个读取数据源的配置,正式代码需改为读取数据表的数据
List<DataSourceConfig> dbList = Lists.newArrayList();

DataSourceConfig db1 = DataSourceConfig.builder().name("datasource1").driverClassName("com.mysql.jdbc.Driver")
.url("jdbc:mysql://127.0.0.1:3306/dataclean1?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false")
.username("root").password("zhaojufei123").build();

DataSourceConfig db2 = DataSourceConfig.builder().name("datasource2").driverClassName("com.mysql.jdbc.Driver")
.url("jdbc:mysql://127.0.0.1:3306/dataclean2?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false")
.username("root").password("zhaojufei123").build();

dbList.add(db1);
dbList.add(db2);

log.info("MybatiesConfig配置信息:" + MybatiesConfig.mapperLocations + "," + MybatiesConfig.configLocation);

dbList.forEach(config -> {

try {

// 1、首先创建数据源
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(config.getDriverClassName());
ds.setUrl(config.getUrl());
ds.setUsername(config.getUsername());
ds.setPassword(config.getPassword());

// 2、创建sqlSessionFactory
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(ds);
// sqlSessionFactory.setConfigLocation(new ClassPathResource(MybatiesConfig.configLocation));
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactory.setMapperLocations(resolver.getResources(MybatiesConfig.mapperLocations));

// 3、创建sqlSessionTemplate
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory.getObject());
sqlSessionTemplateMap.put(config.getName(), sqlSessionTemplate);

// 4、控制事务
DataSourceTransactionManager manager = new DataSourceTransactionManager(ds);// 卡在这里了,不知道怎么写


} catch (Exception e) {
// 创建失败,继续创建下一个
log.error("创建数据源异常,msg = {}, e={}", e.getMessage(), e);
}
});
}
}


dao的代码在这里

package com.zhaojufei.dataclean.clean.dao.impl;

import org.springframework.stereotype.Component;

import com.zhaojufei.dataclean.clean.dao.DataCleanDao;
import com.zhaojufei.dataclean.common.dao.DataSourceHolder;

/**
* 由于同一套代码(dao)需要通过不同的数据源操作数据,操作数据时需要选择数据源
* 所以这个dao做成了普通组件
*/
@Component
public class DataCleanDaoImpl implements DataCleanDao {

/**
* 通过指定数据源执行数据清理,比较建议显示传递参数
*
* @param datasource
*/
@Override
public void clean(String datasource) {

DataSourceHolder.getSqlSessionTemplate(datasource)
.delete("com.zhaojufei.dataclean.clean.dao.DataCleanDao.clean");
}

/**
* 通过指定数据源执行数据清理,比较建议显示传递参数
*
* @param datasource
*/
@Override
public void clean2(String datasource) {

DataSourceHolder.getSqlSessionTemplate(datasource)
.delete("com.zhaojufei.dataclean.clean.dao.DataCleanDao.clean2");
}
}



目前这些代码是可以跑通的,就是没有事务控制。而且我还想用注解事务。



@Override
@Transactional
public void cleanData() {

dataCleanDao.clean("datasource1");

int i = 1 / 0;

dataCleanDao.clean2("datasource1");

}

搞了个service,模拟异常,事务没有回滚。悲剧的是,即便用编程式事务,也不行。
...全文
1430 4 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
maradona1984 2019-08-01
  • 打赏
  • 举报
回复
引用 3 楼 远举高飞 的回复:
[quote=引用 2 楼 maradona1984 的回复:] 动态数据可以放zk/apollo/nacos 数据源就自己实现DataSource接口,依赖具体的数据源实现.比如druid什么的,具体调用哪个数据源我觉得可以用ThreadLocal传递参数,这样优雅一点 这样你就可以用spring的事务了,因为你的多数据源实现的是标准的DataSource接口,当然spring貌似有自己的多数据源实现方案 当然你的例子我觉得事务并非完美可靠,多个数据源并不能这么粗暴的管理,毕竟涉及到分布式事务,这个我觉得你并没有完全理解透彻,你的业务场景可能并不需要做这么复杂的设计,但我不清楚你具体的业务场景,多数据源是单个业务需要,还是整个系统架构需要?
试了编程式事务是可以的。自己的代码问题。 现在是想做个运维工具,可以清理多个数据源的数据。一套微服务系统,按照业务分的库,用户试用后需要清理老数据,然后正式使用。不需要分布式事务,实际上事务也不需要。我自己觉得自己想了这个方案,起码操作一个数据源时需要支持事务,万一将来需要。 [/quote] 那就是单个业务需要多数据源,而且是运维工具,完全不需要做那么复杂保证可靠性 spring是有多数据源的方案的,那自然也是支持注解的,当然想灵活一点,就像我说的那样做 比如某些系统有多租户概念,直接分表分库什么的,当然也有其他的解决方案
赵牧野 2019-08-01
  • 打赏
  • 举报
回复
引用 2 楼 maradona1984 的回复:
动态数据可以放zk/apollo/nacos 数据源就自己实现DataSource接口,依赖具体的数据源实现.比如druid什么的,具体调用哪个数据源我觉得可以用ThreadLocal传递参数,这样优雅一点 这样你就可以用spring的事务了,因为你的多数据源实现的是标准的DataSource接口,当然spring貌似有自己的多数据源实现方案 当然你的例子我觉得事务并非完美可靠,多个数据源并不能这么粗暴的管理,毕竟涉及到分布式事务,这个我觉得你并没有完全理解透彻,你的业务场景可能并不需要做这么复杂的设计,但我不清楚你具体的业务场景,多数据源是单个业务需要,还是整个系统架构需要?
试了编程式事务是可以的。自己的代码问题。 现在是想做个运维工具,可以清理多个数据源的数据。一套微服务系统,按照业务分的库,用户试用后需要清理老数据,然后正式使用。不需要分布式事务,实际上事务也不需要。我自己觉得自己想了这个方案,起码操作一个数据源时需要支持事务,万一将来需要。
maradona1984 2019-08-01
  • 打赏
  • 举报
回复
动态数据可以放zk/apollo/nacos 数据源就自己实现DataSource接口,依赖具体的数据源实现.比如druid什么的,具体调用哪个数据源我觉得可以用ThreadLocal传递参数,这样优雅一点 这样你就可以用spring的事务了,因为你的多数据源实现的是标准的DataSource接口,当然spring貌似有自己的多数据源实现方案 当然你的例子我觉得事务并非完美可靠,多个数据源并不能这么粗暴的管理,毕竟涉及到分布式事务,这个我觉得你并没有完全理解透彻,你的业务场景可能并不需要做这么复杂的设计,但我不清楚你具体的业务场景,多数据源是单个业务需要,还是整个系统架构需要?
赵牧野 2019-08-01
  • 打赏
  • 举报
回复
没人吗?大神来个。@

81,122

社区成员

发帖
与我相关
我的任务
社区描述
Java Web 开发
社区管理员
  • Web 开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧