SpringBoot实现动态数据源切换及单库事务控制
引⾔:
项⽬中经常会遇到多数据源的场景,通常的处理是: 操作数据⼀般都是在DAO层进⾏处理,使⽤配置多个DataSource 然后创建多个SessionFactory,在使⽤Dao层的时候通过不同的SessionFactory进⾏处理,
但是这样的操作代码⼊侵性⽐较明显且配置繁琐难以维护,,,在这⾥推荐⼀个Spring提供的AbstractRoutingDataSource抽象类,它实现了DataSource接⼝的⽤于获取数据库连接的⽅法
AbstractRoutingDataSource的内部维护了⼀个名为targetDataSources的Map,并提供的setter⽅法⽤于设置数据源关键字与数据源的关系,实现类被要求实现其determineCurrentLookupKey()⽅法,由此⽅法的返回值决定具体从哪个数据源中获取连接
⼀. 注解⽅式实现动态切换数据源
原理: AbstractRoutingDataSource提供了程序运⾏时动态切换数据源的⽅法,在dao类或⽅法上标注需要访问数据源的关键字,路由到指定数据源,获取连接
l导⼊相关坐标
<!--mysql相关-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--oracle相关-->
<dependency>
<groupId>aui</groupId>
<artifactId>ojdbc7</artifactId>
<version>${oracle.version}</version>
2.1application.properties配置多数据源
#多数据源配置
#默认数据库(mysql库)
db.default.url=jdbc:mysql://127.0.0.1:3306/demo?connectTimeout=2000&allowMultiQueries=true&rewriteBatchedStatements=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
db.default.username=root
db.default.password=root
#oracle库
l添加datasource配置
spring:
datasource:
group: ${db.groups}
3.1数据源切换⽅法: 维护⼀个static变量datasourceContext⽤于记录每个线程需要使⽤的数据源关键字。并提供切换、读取、清除数据源配置信息的⽅法
编写DataSourceContextHolder类
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static synchronized void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
();
}
public static void clearDataSourceKey() {
}
}
3.2实现AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
private static DynamicDataSource instance;
private static Object lock=new Object();
private static Map<Object,Object> dataSourceMap = wHashMap();
@Override
protected Object determineCurrentLookupKey() {
DataSourceKey();
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
dataSourceMap.putAll(targetDataSources);
super.afterPropertiesSet();
}
public static synchronized DynamicDataSource getInstance(){
if(instance==null){
synchronized (lock){
if(instance==null){
instance=new DynamicDataSource();
}
}
}
return instance;
}
public static boolean isExistDataSource(String key) {
if (StringUtils.isEmpty(key)) {
return false;
}
ainsKey(key);
}
}
3.3编写数据源配置类MybatisConfig
@Configuration
@MapperScan(basePackages = { "com.**.mapper"} , sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfig {
private static Logger LOG = Logger(MybatisConfig.class);
@Autowired
private Environment environment;
private static final String DEFAULT_DATASOURCE_NAME = "default";
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
//需要引⼊mybatis-plus坐标
//MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
/
/bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/wttech/vsm/support/mapper/*.xml")); Object();
}
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource dynamicDataSource() {
String groups = Property("up");
LOG.info("数据源组名称:{}", groups);
Map<Object,Object> dataSourceMap = wHashMap();
Set<String> dbNames = Arrays.asList(groups.split(",")).stream().filter(s -> s.trim().length() > 0).Set());
dbNames.add(DEFAULT_DATASOURCE_NAME);
HikariDataSource first = null;
HikariDataSource def = null;
for (String dbName:dbNames) {
String driver = Property(String.format("db.%s.driver", dbName));
String url = Property(String.format("db.%s.url", dbName));
String username = Property(String.format("db.%s.username", dbName));
String password = Property(String.format("db.%s.password", dbName));
LOG.info("数据源{}连接:{}", dbName, url);
if (StringUtils.isEmpty(url) || StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
continue;
}
DataSourceBuilder<HikariDataSource> hikariDataSourceBuilder = ate().type(HikariDataSource.class);
if (!StringUtils.isEmpty(driver)) {
hikariDataSourceBuilder.driverClassName(driver);
}
HikariDataSource hikariDataSource = hikariDataSourceBuilder.url(url).username(username).password(password).build();
hikariDataSource.setAutoCommit(true);
String testQuery = Property(String.format("db.%s.connectionTestQuery", dbName));
if (!StringUtils.isEmpty(testQuery)) {
hikariDataSource.setConnectionTestQuery(testQuery);
}
String timeout = Property(String.format("db.%s.connectionTimeout", dbName));
if (!StringUtils.isEmpty(timeout)) {
hikariDataSource.setConnectionTimeout(Long.parseLong(timeout));
}
String minimumIdle = Property(String.format("db.%s.minimumIdle", dbName));
if (!StringUtils.isEmpty(minimumIdle)) {
hikariDataSource.setMinimumIdle(Integer.parseInt(minimumIdle));
}
String maximumPoolSize = Property(String.format("db.%s.maximumPoolSize", dbName));
if (!StringUtils.isEmpty(maximumPoolSize)) {
hikariDataSource.setMaximumPoolSize(Integer.parseInt(maximumPoolSize));
}
String idleTimeout = Property(String.format("db.%s.idleTimeout", dbName));
if (!StringUtils.isEmpty(idleTimeout)) {
hikariDataSource.setIdleTimeout(Long.parseLong(idleTimeout));
}
String maxLifetime = Property(String.format("db.%s.maxLifetime", dbName));
if (!StringUtils.isEmpty(maxLifetime)) {
hikariDataSource.setMaxLifetime(Long.parseLong(maxLifetime));
}
hikariDataSource.setPoolName(dbName);
dataSourceMap.put(dbName, hikariDataSource);
if (first == null) {
first = hikariDataSource;
}
if (DEFAULT_DATASOURCE_NAME.equals(dbName)) {
def = hikariDataSource;
}
}
DynamicDataSource dynamicDataSource = Instance();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(def == null ? first : def);
return dynamicDataSource;
}
4.1.标记数据源注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchDataSource {
String value();
}
4.2.编写切⼊点⽅法
@Aspect
@Component
public class MethodInterceptor {
@Around("execution(* com.wttech.vsm.support.mapper..*.*(..)))
public Object dao(ProceedingJoinPoint invocation) throws Throwable {
MethodSignature methodSignature = (MethodSignature) Signature();
Method method = Method();
String dbName = null;
SwitchDataSource dataSource = Annotation(SwitchDataSource.class);
if (dataSource != null) {
dbName = dataSource.value();
if (DynamicDataSource.isExistDataSource(dbName)) {
DataSourceContextHolder.setDataSourceKey(dbName);
}
}
try {
return invocation.proceed();
} finally {
DataSourceContextHolder.clearDataSourceKey();springboot原理pdf
}
}
}
⼆.单库事务控制:
本⼈涉及到到的业务场景是在service层的⼀个⽅法中存在多个dao操作(只涉及单库),,需要维持事务性,,,遇到问题: 数据源是在mapper层通过注解切换的,,@Transactional在services层控制,,导致程序报错不到数据源,,开始的解决思路是前提@SwitchDataSource注解⾄service层 ,,但经测试后还是报错,,,
最后的解决思路是必须保证切换数据源是在事务控制开启之前完成...
1.切⼊点表达式增加service层切⼊
@Around("execution(* com.**.mapper..*.*(..)) || execution(* com.**.service.DataFixService.*(..))")
2.注释掉需要事务控制的dao操作设计到的mapper层的数据源切换注解
// @SwitchDataSource("toll")
int updateFixInList(@Param("listId") String listId, @Param("fieldName") String fieldName, @Param("fieldValue") String fieldValue);
3.事务控制是基于数据源的,,必须在数据源切换后在开启事务,,以下是具体实现思路:
控制器->⽅法1(切换数据源,使⽤代理⽅式调⽤⽅法2)->⽅法2(开启事务,执⾏多个dao操作)
先切换数据源
@SwitchDataSource("toll")
public void updateFixInList(String listId, String fieldName, String fieldValue) {
//springAOP的⽤法中,只有代理的类才会被切⼊,我们在controller层调⽤service的⽅法的时候,是可以被切⼊的,但是如果我们在service层 A⽅法中,调⽤B⽅法,切点切的是B⽅法,那么这时候是不会切⼊的 //通过((Service)AopContext.currentProxy()).B() 来调⽤B⽅法,这样⼀来,就能切⼊了!
((DataFixService) AopContext.currentProxy()).updateFixInListProxy(listId, fieldName, fieldValue);
}
再控制事务
@Transactional(rollbackFor = Exception.class)
public void updateFixInListProxy(String listId, String fieldName, String fieldValue) {
dataFixMapper.updateFixInList(listId, fieldName, fieldValue);
//其他dao操作
}
特别注意: 以上这种⽅法⽬前仅适⽤于多数据源下单库的事务操作,,,如果serviece⽅法dao操作设计多库,,由于⽬前业务场景暂未涉及到,,所以暂未深⼊研究.....
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论