springboot2.6+Mybatis静态多数据源(集成JTA(Atomikos案例)实现分布式事务控制)

多数据源系列

1、springboot2.6+Mybatis静态多数据源(集成JTA(Atomikos案例)实现分布式事务控制)
2、springboot2.6+Mybatis动态多数据源AOP切换(AbstractRoutingDataSource)
3、springboot2.6+Mybatis注解多数据源使用dynamic-datasource-spring-boot-starter为依赖

说明

随着应用用户数量的增加,相应的并发请求的数量也会跟着不断增加,慢慢地,单个数据库已经没有办法满足我们频繁的数据库操作请求了。
在某些场景下,我们可能会需要配置多个数据源,使用多个数据源(例如实现数据库的读写分离)来缓解系统的压力等,同样的,SpringBoot官方提供了相应的实现来帮助开发者们配置多数据源,据我目前所了解到的,一般分为两种方式静态与动态(AOP和dynamic)。
本文使用的是静态的方式。而且加了将spring原有的数据源信息管理的类型改为atomikos实现的AtomikosDataSourceBean数据源类(他也是javax.sql.DataSource的实现类),该类有一个属性是xaDataSource(xa就是分布式事务处理的一种模型规范),实现一下xaDataSource赋给这个属性;
之后各个数据源不要再独自去声明事务控制对象了,因为这时候Atomikos会自动有一个统一的分布式事务控制对象来控制事务。

本文中数据库用的是mysql5.7。完整代码地址在结尾!!

为什么用多数据源

其实在系统设计时,应当尽量避免一个项目接入多个数据源。我们应该尽量收敛一个数据库的使用者,这样在后续进行一些数据迁移、数据库重构等工作时能够降低风险和难度。 当然,这并不是绝对的情况,所谓“存在即是合理”。多个数据源的使用从另一方面来说能够大大的降低编码便捷性。我们不再需要通过Dubbo、SpringCloud等方式去通过其他系统中获取相关的数据。

简介

在大部分情况下,搭建单数据源就能够满足需求了,但是特殊情况下也需要使用多数据源,这里就写了一个demo搭建多数据源,注意这里没有使用AbstractRoutingDataSource 来实现动态切换
静态方式方式说白了,其实就是根据不同的包路径定义多个数据源,想用哪个用哪个呗。

静态数据源方案

文件结构

最好每个数据源的mapper对应每个目录,分开来做。
在这里插入图片描述

maven引入:

     <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
 </dependency>
 <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
 </dependency>

application.yml 配置文件

master1:
  url: jdbc:mysql://127.0.0.1:3306/master1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
  username: root
  password: 123456
  driverClassName: com.mysql.cj.jdbc.Driver
master2:
  url: jdbc:mysql://127.0.0.1:3306/master2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
  username: root
  password: 123456
  driverClassName: com.mysql.cj.jdbc.Driver
oracle:
  url: jdbc:oracle:thin:@10.132.212.63:1688:TESTDB
  username: flx
  password: flx202108
  driverClassName: oracle.jdbc.OracleDriver



logging:
  level:
    com.xkcoding: debug
    com.xkcoding.orm.mybatis.mapper: trace
    
server:
  port: 8080
#  servlet:
#    context-path: /demo

如下代码片段配置了其中一个数据源的参数

@Configuration
@MapperScan(basePackages = Master1DataSourceConfig.PACKAGE, sqlSessionTemplateRef = "master1SqlSessionTemplate")
public class Master1DataSourceConfig {
    static final String PACKAGE = "com.orm.mybatis.mapper.master1";
    static final String MAPPER_LOCATION = "classpath:mapper/master1/*.xml";

    @Value("${master1.url}")
    private String url;

    @Value("${master1.username}")
    private String user;

    @Value("${master1.password}")
    private String password;

    @Value("${master1.driverClassName}")
    private String driverClass;

//非分布式事务数据源 (多数据源事务情况下只能回滚单个数据源)
//因为事务会统一交给Atomikos全局事务,(因为是用了AtomikosDataSourceBean管理数据源),
//所以不能添加其他事务管理器
//    @Bean(name = "master1DataSource")     //非分布式事务数据源 (多数据源事务情况下只能回滚单个数据源)
//    public DataSource master1DataSource() {
        DataSource dataSource = DataSourceBuilder.create().driverClassName(driverClass).password(password).url(url).username(user).build();
//         HikariDataSource hikariDataSource = new HikariDataSource();
//         hikariDataSource.setJdbcUrl(url);
//         hikariDataSource.setUsername(user);
//         hikariDataSource.setDriverClassName(driverClass);
//         hikariDataSource.setPassword(password);
//         hikariDataSource.setMinimumIdle(5);
//         hikariDataSource.setMaximumPoolSize(20);
//         hikariDataSource.setAutoCommit(true);
//         hikariDataSource.setPoolName("SpringBootDemoHikariCP");
//         hikariDataSource.setMaxLifetime(60000);
//         hikariDataSource.setConnectionTimeout(30000);
//        return hikariDataSource;
//    }

    @Bean(name = "master1DataSource")    //分布式事务数据源(多数据源事务情况下只能回滚单个数据源)
    public DataSource master1DataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(password);
        mysqlXaDataSource.setUser(user);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        // 将本地事务注册到创 Atomikos全局事务
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("master1DataSource");
        xaDataSource.setMinPoolSize(5);
        xaDataSource.setMaxPoolSize(20);
        xaDataSource.setMaxLifetime(60000);
        xaDataSource.setBorrowConnectionTimeout(30);
        xaDataSource.setLoginTimeout(30);
        xaDataSource.setMaintenanceInterval(60);
        xaDataSource.setMaxIdleTime(60);
        return xaDataSource;
    }

// 非分布式事务(多数据源事务情况下只能回滚单个数据源)
//    @Bean(name = "master1TransactionManager")
//    public DataSourceTransactionManager masterTransactionManager(@Qualifier("master1DataSource") DataSource masterDataSource) {
//        return new DataSourceTransactionManager(masterDataSource);
//  }

    @Bean(name = "master1SqlSessionFactory")
    @Primary
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("master1DataSource") DataSource masterDataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(masterDataSource);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);//驼峰
        sessionFactory.setConfiguration(configuration);
        sessionFactory.setTypeAliasesPackage("com.orm.mybatis.entity");
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(Master1DataSourceConfig.MAPPER_LOCATION));
        return sessionFactory.getObject();
    }

    @Bean(name = "master1SqlSessionTemplate")         //添加这个就不用@primary
    public SqlSessionTemplate test2SqlSessionTemplate(@Qualifier("master1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

如上配置完成了一个数据源的添加,其余数据源按此模板进行复制便可以,但是有一点值得注意,需要将各个DataSource和SqlSessionFactory的Bean名称进行区分并搭配@Qualifier进行选择,不然会导致各个数据源间调用错乱。

UserServiceImpl 业务层

    @Service
    public class UserServiceImpl {

    @Resource
    private UserMapper1 userMapper1;

    @Resource
    private UserMapper2 userMapper2;

    @Resource
    private AsusPoInfoMapper3 asusPoInfoMapper3;
    @Transactional
    public void testTransitional() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date =  simpleDateFormat.format(new Date());
        String UUID = java.util.UUID.randomUUID().toString().substring(0,5);
        User user = User.builder().email("andrew@qq.com"+UUID).name("andrew"+UUID).password("123456"+UUID).phoneNumber("123"+UUID)
                .lastUpdateTime(date).createTime(date).status(0).salt("password"+UUID).build();
        AsusPoInfo asusPoInfo = AsusPoInfo.builder().id(java.util.UUID.randomUUID().toString().substring(0,20))
                .woNo("andrew").po("123456").poLine("poline").cPo("cpo123456").shipType("Direct").build();
        userMapper1.saveUser(user);
        userMapper2.saveUser(user);
        asusPoInfoMapper3.insertAsusPoInfo(asusPoInfo);
        throw new RuntimeException();
    }

测试多数据源回滚

public class UserTest extends AndrewApplicationTests {

    @Resource
    private UserServiceImpl userService;

    @Test
    public void test3(){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");   
        String date =  simpleDateFormat.format(new Date());
        User user = User.builder().email("andrew@qq.com").name("andrew").password("123456").
        phoneNumber("123").lastUpdateTime(date).createTime(date).status(0).salt("MD5").build();
        userService.insertUser(user);
    }
}

测试结果,三个数据源都回滚插入的数据。

方案的权衡

  1. 静态多数据源方案优势在于配置简单并且对业务代码的入侵性极小,缺点也显而易见:我们需要在系统中占用一些资源,而这些资源并不是一直需要,一定程度上会造成资源的浪费。如果你需要在一段业务代码中同时使用多个数据源的数据又要去考虑操作的原子性(事务)可以用spring的jta实现事务,那么这种方案无疑会适合你。
  2. (aop和dynamic)动态数据源(AbstractRoutingDataSource)方案配置上看起来配置会稍微复杂一些,但是很好的符合了“即拿即用,即用即还”的设计原则,我们把多个数据源看成了一个池子,然后进行消费。它的缺点正如上文所暴露的那样:我们往往需要在事务的需求下做出妥协。而且由于需要切换环境上下文,在高并发量的系统上进行资源竞争时容易发生死锁等活跃性问题。我们常用它来进行数据库的“读写分离”,不需要在一段业务中同时操作多个数据源。这种动态形式并不能用spring的jta实现,而且其他实现方式(seata等)虽然可以实现,但配置复杂且实用度不高。
  3. 如果需要使用事务,一定记得使用分布式事务进行Spring自带事务管理的替换,否则将无法进行一致性控制。
  4. 写到这里本文也就结束,好久没有撰写文章很多东西考虑不是很详尽,谢谢批评指正!

项目地址

springboot2.6+mybatis
https://gitee.com/liuweiqiang12/springboot-mybatis-static-datasource

springboot2.6+mybatis-plus
https://gitee.com/liuweiqiang12/springboot-mybatis-plus-static-datasource

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 首先,为了使用多数据源分布式事务,我们需要添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.6</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> ``` 接下来,我们需要在application.properties文件中配置数据源和事务管理器: ```properties # 配置主数据源 spring.datasource.url=jdbc:mysql://localhost:3306/main_db?characterEncoding=utf8&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver # 配置从数据源 spring.datasource.slave.url=jdbc:mysql://localhost:3306/slave_db?characterEncoding=utf8&useSSL=false spring.datasource.slave.username=root spring.datasource.slave.password=root spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver # 配置Mybatis mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.example.entity # 配置Druid数据源 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.initial-size=1 spring.datasource.druid.max-active=10 spring.datasource.druid.min-idle=1 spring.datasource.druid.max-wait=60000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 spring.datasource.druid.test-while-idle=true spring.datasource.druid.test-on-borrow=false spring.datasource.druid.test-on-return=false spring.datasource.druid.filters=stat,wall,log4j spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 配置事务管理器 spring.transaction.default-timeout=600 spring.transaction.rollback-on-commit-failure=true spring.transaction.allow-bean-definition-overriding=true spring.transaction.jta.registry-name=atomikos spring.jta.enabled=true spring.jta.atomikos.connectionfactory.min-pool-size=5 spring.jta.atomikos.connectionfactory.max-pool-size=10 spring.jta.atomikos.connectionfactory.borrow-connection-timeout=30 spring.jta.atomikos.connectionfactory.max-idle-time=60 spring.jta.atomikos.connectionfactory.concurrency-level=100 ``` 然后,我们需要创建两个数据源的配置类,分别为主数据源和从数据源: ```java @Configuration @MapperScan(basePackages = "com.example.mapper.main", sqlSessionTemplateRef = "mainSqlSessionTemplate") public class MainDataSourceConfig { @Bean(name = "mainDataSource") @ConfigurationProperties(prefix = "spring.datasource") public DataSource mainDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "mainSqlSessionFactory") public SqlSessionFactory mainSqlSessionFactory(@Qualifier("mainDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/main/*.xml")); return bean.getObject(); } @Bean(name = "mainTransactionManager") public DataSourceTransactionManager mainTransactionManager(@Qualifier("mainDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "mainSqlSessionTemplate") public SqlSessionTemplate mainSqlSessionTemplate(@Qualifier("mainSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } ``` ```java @Configuration @MapperScan(basePackages = "com.example.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate") public class SlaveDataSourceConfig { @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "slaveSqlSessionFactory") public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml")); return bean.getObject(); } @Bean(name = "slaveTransactionManager") public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "slaveSqlSessionTemplate") public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } ``` 最后,我们需要在事务管理器上添加注解@EnableTransactionManagement,并在需要使用事务的方法上添加注解@Transactional: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class, transactionManager = "mainTransactionManager") @Override public void save(User user) { userMapper.insert(user); } @Transactional(rollbackFor = Exception.class, transactionManager = "slaveTransactionManager") @Override public User findById(int id) { return userMapper.selectByPrimaryKey(id); } } ``` 以上就是使用SpringBoot+Mybatis+druid多数据源分布式事务的基本步骤。 ### 回答2: Spring Boot是一个用于构建独立的、生产级的应用程序的框架。它简化了应用程序的开发过程,并通过自动配置来减少了繁琐的配置。MyBatis是一个ORM(对象关系映射)框架,它提供了将数据库操作映射到Java对象的功能。Druid是一种高性能的数据库连接池。 要在Spring Boot中使用MyBatis和Druid进行多数据源配置和分布式事务管理,可以按照以下步骤进行操作: 1. 添加依赖:在项目的pom.xml文件中,添加Spring Boot、MyBatis和Druid的依赖。 2. 配置数据源:在application.properties文件中,配置并命名多个数据源,设置数据库连接等信息。 3. 创建数据源配置类:创建一个配置类,使用@Configuration注解将其标记为配置类,并使用@ConfigurationProperties注解将数据源属性注入。 4. 创建数据源:根据配置类中的属性,创建多个数据源,并将其加入到数据源路由器中。 5. 配置MyBatis:创建一个配置类,使用@MapperScan注解设置MyBatis的mapper接口路径,并将数据源注入到SqlSessionFactory中。 6. 配置分布式事务:使用@EnableTransactionManagement注解启用事务管理,并配置事务管理器。 7. 编写数据库操作代码:在mapper接口中定义数据库操作方法,并在Service层中调用这些方法进行数据库操作。 通过以上步骤,你就可以在Spring Boot项目中完成MyBatis和Druid的多数据源配置和分布式事务管理。不过需要注意的是,使用多数据源分布式事务会增加项目的复杂性和性能开销,所以在使用之前需要仔细考虑是否真正需要这些功能。 ### 回答3: Spring Boot是一种快速构建Java应用程序的框架,MyBatis是一种流行的Java持久化框架,Druid是一种高性能的数据库连接池。本文将介绍如何在Spring Boot中使用MyBatis和Druid来实现多数据源分布式事务。 要使用多个数据源,我们首先需要配置多个数据源。在Spring Boot中,我们可以通过在application.properties或者application.yml文件中配置多个数据源的连接信息。我们需要为每个数据源指定不同的URL、用户名和密码。然后,我们可以使用@Primary和@Qualifier来指定主数据源和其他数据源。 在配置数据源后,我们需要配置MyBatis来使用这些数据源。我们可以通过创建多个SqlSessionFactory来实现多数据源,然后在每个SqlSessionFactory中设置相应的数据源。我们还可以使用@MapperScan注解来自动扫描和注册Mapper接口。 在使用MyBatis和多个数据源时,我们可能会遇到事务管理的问题。为了解决这个问题,我们可以使用Spring Boot提供的@Transactional注解来标记需要进行事务管理的方法,然后Spring Boot会自动为我们处理事务。对于需要跨多个数据源进行事务管理的情况,我们可以使用JTA(Java Transaction API)实现分布式事务。在Spring Boot中,我们可以使用Atomikos或Bitronix等JTA提供商来实现分布式事务。 总结起来,使用Spring Boot、MyBatis和Druid,我们可以很容易地实现多数据源分布式事务。通过正确配置数据源和使用相关注解,我们可以在几分钟内完成这些任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶孤崖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值