spring+atomikos+mybatis 多数据源事务(动态切换)

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS AI 助手,专业版
简介:
注:自动切换,是为不同的数据源,却要对应相同的dao层;

1.与无事务版的一样,创建DynamicDataSource类,继承AbstractRoutingDataSource

package com.test.main.dataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDataSource();
    }

}
创建辅助类DynamicDataSourceHolder,主要用于保存当前线程所需的datasource的key值

package com.test.main.dataSource;

public class DynamicDataSourceHolder {
    private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();

    public static String getDataSource() {
        return THREAD_DATA_SOURCE.get();
    }

    public static void setDataSource(String dataSource) {
        THREAD_DATA_SOURCE.set(dataSource);
    }

    public static void clearDataSource() {
        THREAD_DATA_SOURCE.remove();
    }
}

创建dao层切面,注解选择数据源DataSourceAspect类:

package com.test.main.dataSource;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import com.test.main.annotations.DataSource;

@Component
@Aspect
public class DataSourceAspect {
    @Before("execution(* com.test.model.dao.*.*.*(..))")
    public void intercept(JoinPoint point) throws Exception {
        Class<?> target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        for (Class<?> clazz : target.getInterfaces()) {
            resolveDataSource(clazz, signature.getMethod());
        }
        resolveDataSource(target, signature.getMethod());

    }
    private void resolveDataSource(Class<?> clazz, Method method) {
        String sourceName = null;
        try {
            Class<?>[] types = method.getParameterTypes();
            if (clazz.isAnnotationPresent((Class<? extends Annotation>) DataSource.class)) {
                DataSource source = clazz.getAnnotation(DataSource.class);
                sourceName = source.value();
                DynamicDataSourceHolder.setDataSource(sourceName);
            }
            Method m = clazz.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                DataSource source = m.getAnnotation(DataSource.class);
                sourceName = source.value();
                DynamicDataSourceHolder.setDataSource(sourceName);
            }
        } catch (Exception e) {
            System.out.println(clazz + ":" + e.getMessage());
        }
    }

}
2.spring-db.xml 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">

    <util:properties id="jdbc"
        location="classpath:etc/mybatis/db.properties" />

    <!-- 连接池配置开始 -->
    <bean id="chicken" class="com.atomikos.jdbc.AtomikosDataSourceBean"
        init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName"
            value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="uniqueResourceName" value="mysql/chicken" />
        <property name="xaProperties">
            <props>
                <prop key="user">#{jdbc.username}</prop>
                <prop key="password">#{jdbc.password}</prop>
                <prop key="url">#{jdbc.chicken}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="20" />
        <property name="maxIdleTime" value="60" />
        <property name="reapTimeout" value="20000" />
    </bean>
    <bean id="test_chicken" class="com.atomikos.jdbc.AtomikosDataSourceBean"
        init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName"
            value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="uniqueResourceName" value="mysql/test_chicken" />
        <property name="xaProperties">
            <props>
                <prop key="user">#{jdbc.username}</prop>
                <prop key="password">#{jdbc.password}</prop>
                <prop key="url">#{jdbc.c_test}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="20" />
        <property name="maxIdleTime" value="60" />
        <property name="reapTimeout" value="20000" />
    </bean>

    <bean id="fariyking" class="com.atomikos.jdbc.AtomikosDataSourceBean"
        init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName"
            value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="uniqueResourceName" value="mysql/fariyking" />
        <property name="xaProperties">
            <props>
                <prop key="user">#{jdbc.username}</prop>
                <prop key="password">#{jdbc.password}</prop>
                <prop key="url">#{jdbc.fariyking}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="20" />
        <property name="maxIdleTime" value="60" />
        <property name="reapTimeout" value="20000" />
    </bean>
    <bean id="test_fariyking" class="com.atomikos.jdbc.AtomikosDataSourceBean"
        init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName"
            value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="uniqueResourceName" value="mysql/test_fariyking" />
        <property name="xaProperties">
            <props>
                <prop key="user">#{jdbc.username}</prop>
                <prop key="password">#{jdbc.password}</prop>
                <prop key="url">#{jdbc.f_test}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="20" />
        <property name="maxIdleTime" value="60" />
        <property name="reapTimeout" value="20000" />
    </bean>


    <!-- 连接池配置结束 -->

    <!-- MyBatis整合开始 -->

    <bean id="c_dynamicDataSource" class="com.test.main.dataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="chicken" value-ref="chicken"></entry>
                <entry key="test_chicken" value-ref="test_chicken"></entry>
            </map>
        </property>
    </bean>
    <bean id="cFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="c_dynamicDataSource" />
        <property name="configLocation" value="classpath:etc/mybatis/mybatis-config.xml" />
        <property name="mapperLocations" value="classpath:com/test/**/*.xml" />
    </bean>    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.test.model.dao.c" />
        <property name="annotationClass" value="com.test.main.annotations.MyBatisRepository" />
        <property name="sqlSessionFactory" ref="cFactory"></property>
        <property name="sqlSessionFactoryBeanName" value="cFactory"></property>
    </bean>
    
    <bean id="f_dynamicDataSource" class="com.test.main.dataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="fariyking" value-ref="fariyking"></entry>
                <entry key="test_fariyking" value-ref="test_fariyking"></entry>
            </map>
        </property>
    </bean>
    <bean id="fFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="f_dynamicDataSource" />
        <property name="configLocation" value="classpath:etc/mybatis/mybatis-config.xml" />
        <property name="mapperLocations" value="classpath:com/test/**/*.xml" />
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.test.model.dao.f" />
        <property name="annotationClass" value="com.test.main.annotations.MyBatisRepository" />
        <property name="sqlSessionFactory" ref="fFactory"></property>
        <property name="sqlSessionFactoryBeanName" value="fFactory"></property>
    </bean>
    <!-- MyBatis整合结束 -->


    <!-- 配置数据库事务开始 -->
    <!-- atomikos事务管理器 -->
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
        init-method="init" destroy-method="close">
        <description>UserTransactionManager</description>
        <property name="forceShutdown">
            <value>true</value>
        </property>
    </bean>
    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="3000" />
    </bean>
    <bean id="springTransactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager">
            <ref bean="atomikosTransactionManager" />
        </property>
        <property name="userTransaction">
            <ref bean="atomikosUserTransaction" />
        </property>
        <property name="allowCustomIsolationLevels" value="true" />
    </bean>

    <tx:annotation-driven transaction-manager="springTransactionManager" />
    <!-- 配置数据库事务结束 -->

</beans>

2.1在同一个事务中,不同的数据源需要不同 SqlSessionFactoryBean,注意配置时,需要配置 MapperScannerConfigurer的:


<property name="sqlSessionFactoryBeanName" value="cFactory"></property>

value值为:SqlSessionFactoryBean 的 bean id;

2.2同一事务中的SqlSessionFactoryBean,对应MapperScannerConfigurer 中的 basePackage 不能范围重合,不然在同一事务时,spring不会切换数据源,而是取先前与之重合的SqlSessionFactoryBean的数据源









本文转自yunlielai51CTO博客,原文链接:http://blog.51cto.com/4925054/2046456,如需转载请自行联系原作者


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
6月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
12月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
342 0
|
12月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1591 0
|
12月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
308 0
|
7月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1029 1
|
10月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
882 10
|
9月前
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
696 1
|
9月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
1487 1
Spring boot 使用mybatis generator 自动生成代码插件
|
12月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
920 0
|
前端开发 Java 数据库连接
Java后端开发-使用springboot进行Mybatis连接数据库步骤
本文介绍了使用Java和IDEA进行数据库操作的详细步骤,涵盖从数据库准备到测试类编写及运行的全过程。主要内容包括: 1. **数据库准备**:创建数据库和表。 2. **查询数据库**:验证数据库是否可用。 3. **IDEA代码配置**:构建实体类并配置数据库连接。 4. **测试类编写**:编写并运行测试类以确保一切正常。
692 2