多数据源模拟分布式事务之SpringBoot+Jta-Atomikos+Jpa

0.实体类

package com.example.jta.entity.db1;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "goods")
@Data
public class Goods {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    private String name;

}
package com.example.jta.entity.db2;

import lombok.Data;
import lombok.ToString;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity
@Table(name = "user")
@Data
@ToString
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    @Column
    private String username;

    @Column
    private Date birthday;

    @Column
    private String sex;

    @Column
    private String address;

}

布局图

多数据源模拟分布式事务之SpringBoot+Jta-Atomikos+Jpa 

1.引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>jta-atomikos</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jta-atomikos</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</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-jta-atomikos</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--版本不能太高-->
            <version>8.0.11</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.Yaml配置

spring:
  datasource:
    db1:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
      username: root
      password: root
    db2:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
      username: root
      password: root
  jpa:
    database: mysql
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL57Dialect
    #对于多数据源必须要这样设置,否则会报错
    properties:
      hibernate.hbm2ddl.auto: update
server:
  port: 10010

3.数据源配置

package com.example.jta.config;

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Objects;

/**
 * 商品数据源配置
 */
@Configuration
@EnableJpaRepositories(basePackages = {"com.example.jta.dao.db1"}, entityManagerFactoryRef = "goodsEntityManagerFactory", transactionManagerRef = "goodsTransactionManager")
public class GoodsDataSourceConfig {

    @Resource
    private JpaProperties jpaProperties;

    @Value("${spring.datasource.db1.jdbc-url}")
    private String jdbcUrl;

    @Value("${spring.datasource.db1.username}")
    private String username;

    @Value("${spring.datasource.db1.password}")
    private String password;

    /**
     * 第一个数据源
     */
    @Primary
    @Bean(name = "goodsDataSource")
    public DataSource goodsDataSource() {
        DruidXADataSource druidXADataSource = new DruidXADataSource();
        druidXADataSource.setUrl(jdbcUrl);
        druidXADataSource.setUsername(username);
        druidXADataSource.setPassword(password);
        druidXADataSource.setDefaultAutoCommit(false);
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(druidXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("goodsDataSource");
        atomikosDataSourceBean.setPoolSize(5);

        return atomikosDataSourceBean;
    }

    /**
     * 商品实体类管理工厂
     */
    @Primary
    @Bean(name = "goodsEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean goodsEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(goodsDataSource())
                .properties(jpaProperties.getProperties())
                //设置实体类所在位置:类或包
                .packages("com.example.jta.entity.db1")
                //持久化单元名称
                .persistenceUnit("goodsPersistenceUnit")
                .build();
    }

    /**
     * 商品事务管理器
     */
    @Primary
    @Bean(name = "goodsTransactionManager")
    public PlatformTransactionManager goodsTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(Objects.requireNonNull(goodsEntityManagerFactory(builder).getObject()));
    }

}
package com.example.jta.config;

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Objects;

/**
 * 用户数据源配置
 */
@Configuration
@EnableJpaRepositories(basePackages = {"com.example.jta.dao.db2"},entityManagerFactoryRef = "userEntityManagerFactory",transactionManagerRef = "userTransactionManager")
public class UserDataSourceConfig {

    /**
     * 自动注入jpa配置
     */
    @Resource
    private JpaProperties jpaProperties;

    @Value("${spring.datasource.db2.jdbc-url}")
    private String jdbcUrl;

    @Value("${spring.datasource.db2.username}")
    private String username;

    @Value("${spring.datasource.db2.password}")
    private String password;

    @Bean(name = "userDataSource")
    public DataSource userDataSource() {
        DruidXADataSource druidXADataSource = new DruidXADataSource();
        druidXADataSource.setUrl(jdbcUrl);
        druidXADataSource.setUsername(username);
        druidXADataSource.setPassword(password);
        druidXADataSource.setDefaultAutoCommit(false);

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(druidXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("userDataSource");
        atomikosDataSourceBean.setPoolSize(5);

        return atomikosDataSourceBean;
    }

    /**
     * 将数据源、连接池、以及其他配置策略进行封装返回给事务管理器
     * 自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
    */
    @Bean(name = "userEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean userEntityManagerFactory(EntityManagerFactoryBuilder builder){
      return builder.dataSource(userDataSource())
              .properties(jpaProperties.getProperties())
              //设置实体类所在位置:类或包
              .packages("com.example.jta.entity.db2")
              //持久化单元名称
              .persistenceUnit("userPersistenceUnit")
              .build();
    }

    /**
      * 返回数据源的事务管理器
      */
    @Bean(name = "userTransactionManager")
    public PlatformTransactionManager userTransactionManager(EntityManagerFactoryBuilder builder){
        return new JpaTransactionManager(Objects.requireNonNull(userEntityManagerFactory(builder).getObject()));
    }

}
package com.example.jta.config;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.UserTransaction;

/**
 * 统一事务管理
 */
@Configuration
public class JtaTransactionManagerConfig {

    @Primary
    @Bean(name = "jtaTransactionManager")
    public JtaTransactionManager regTransactionManager () {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }

}

4.控制层Controller

package com.example.jta.controller;

import com.example.jta.entity.db1.Goods;
import com.example.jta.entity.db2.User;
import com.example.jta.service.GoodsService;
import com.example.jta.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class TransactionController {

    @Autowired
    private UserService userService;

    @Autowired
    private GoodsService goodsService;

    @Transactional(value = "jtaTransactionManager", rollbackFor = Exception.class)
    @GetMapping("/add")
    public ResponseEntity addTest() {
        Goods goods = new Goods();
        goods.setId(1L);
        goods.setName("涡轮发动机");
        goodsService.addGoods(goods);

        User user = new User();
        user.setSex("男");
        user.setUsername("周杰伦");
        user.setAddress("上海");
        user.setBirthday(new Date());
        userService.addUser(user);

        return ResponseEntity.ok("添加成功");
    }

    //在该接口中故意制造一个除零异常
    @Transactional(value = "jtaTransactionManager", rollbackFor = Exception.class)
    @GetMapping("/add2")
    public ResponseEntity addTest2() {
        Goods goods = new Goods();
        goods.setId(2L);
        goods.setName("兰博基尼");
        goodsService.addGoods(goods);

        User user = new User();
        user.setSex("男");
        user.setUsername("周润发");
        user.setAddress("上海");
        user.setBirthday(new Date());
        userService.addUser(user);

        int i = 10 / 0;

        return ResponseEntity.ok("添加成功");
    }

}

5.业务层Service

package com.example.jta.service;

import com.example.jta.entity.db1.Goods;

import java.util.List;

public interface GoodsService {

    List<Goods> findAll();

    Goods addGoods(Goods goods);

}
package com.example.jta.service;

import com.example.jta.entity.db2.User;

import java.util.List;

public interface UserService {

    List<User> findAll();

    User addUser(User user);

}
package com.example.jta.service.impl;

import com.example.jta.dao.db1.GoodsRepository;
import com.example.jta.entity.db1.Goods;
import com.example.jta.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.List;

@Service
@Transactional(transactionManager = "goodsTransactionManager",rollbackFor = Exception.class,propagation = Propagation.NEVER)
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Override
    public List<Goods> findAll() {
        return goodsRepository.findAll();
    }

    @Override
    public Goods addGoods(Goods goods) {
        try {
            return goodsRepository.save(goods);
        }catch (Exception e){
            //强制手动事务回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return null;
    }

}
package com.example.jta.service.impl;

import com.example.jta.dao.db2.UserRepository;
import com.example.jta.entity.db2.User;
import com.example.jta.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.List;

@Service
@Transactional(transactionManager = "userTransactionManager", rollbackFor = Exception.class, propagation = Propagation.NEVER)
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public List<User> findAll() {
        return userRepository.findAll();
    }

    @Override
    public User addUser(User user) {
        try {
            return userRepository.save(user);
        } catch (Exception e) {
            //强制手动事务回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return null;
    }
}

6.数据层Dao

package com.example.jta.dao.db1;

import com.example.jta.entity.db1.Goods;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface GoodsRepository extends JpaRepository<Goods, Long>, JpaSpecificationExecutor<Goods> {
}
package com.example.jta.dao.db2;

import com.example.jta.entity.db2.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}

7.启动类

package com.example.jta;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
//手动进行事务管理
@EnableTransactionManagement
public class JtaAtomikosApplication {

    public static void main(String[] args) {
        SpringApplication.run(JtaAtomikosApplication.class, args);
    }

}

上一篇:通用mapper中selectByExample 详解


下一篇:搭载Dubbo+Zookeeper踩了这么多坑,我终于决定写下这篇!