Spring Boot入门样例-153-JPA-atomikos整合Atomikos处理多数据库分布式事务

Spring Boot入门样例-153-JPA-atomikos整合Atomikos处理多数据库分布式事务

比如用户下单成功,必须扣减用户的余额,此时两个库的数据需要同时成功或者同时失败。本demo演示如何使用jpa hibernate整合Atomikos进行多数据库分布式事务操作

前言

本Spring Boot入门样例准备工作参考:

  • Spring Boot入门样例-001-Java和Maven安装配置
  • Spring Boot入门样例-003-idea 安装配置和插件
  • Spring Boot入门样例-005-如何运行

pox.xml

必要的依赖如下,具体参见该项目的pox.xml

        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.springframework.boot
            spring-boot-starter-jta-atomikos
        

        
            mysql
            mysql-connector-java
        
        
            org.projectlombok
            lombok
            true
        

配置文件

数据表结构参考根目录下的db.sql

USE springbootdemo;
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

USE springbootdemo1;
DROP TABLE IF EXISTS `tbl_order`;
CREATE TABLE `tbl_order` (
  `id` varchar(255) NOT NULL,
  `student_id` varchar(255) NOT NULL,
  `sn` varchar(255) NOT NULL,
  `amount` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

resources/application.yml配置内容

spring:
  datasource:
    user:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/springbootdemo?useUnicode=true&characterEncoding=utf-8&useSSL=false&useAffectedRows=true
      username: root
      password: root
    order:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/springbootdemo1?useUnicode=true&characterEncoding=utf-8&useSSL=false&useAffectedRows=true
      username: root
      password: root

  jpa:
    show-sql: true
    generate-ddl: true
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL55Dialect

代码解析

该项目有很多目录,分别说明如下:

  • controller目录为控制器文件
  • entity目录为实体目录,对应表格中的字段
  • dao目录数据存取对象
  • service为服务接口目录
  • service/impl为服务接口具体实现目录
  • util为工具类目录,加入分布式id雪花算法

以上解释可以参考Spring Boot入门样例-151-JPA整合Jpa多数据源Multi Source

public class AtomikosJtaPlatform extends AbstractJtaPlatform {

    private static final long serialVersionUID = 1L;

    static TransactionManager transactionManager;
    static UserTransaction transaction;

    @Override
    protected TransactionManager locateTransactionManager() {
        return transactionManager;
    }

    @Override
    protected UserTransaction locateUserTransaction() {
        return transaction;
    }
}

DataSourceConfig.java 配置文件解析

@Configuration
@ComponentScan
@EnableTransactionManagement
public class DataSourceConfig {

    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }


    //设置JPA特性
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        //显示sql
        hibernateJpaVendorAdapter.setShowSql(true);
        //自动生成/更新表
        hibernateJpaVendorAdapter.setGenerateDdl(true);
        //设置数据库类型
        hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
        return hibernateJpaVendorAdapter;
    }

    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        AtomikosJtaPlatform.transactionManager = userTransactionManager;
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({"userTransaction", "atomikosTransactionManager"})
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransaction userTransaction = userTransaction();
        AtomikosJtaPlatform.transaction = userTransaction;
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }

}

UserConfig 用户表所在数据源配置

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(
        entityManagerFactoryRef = "userEntityManager",
        transactionManagerRef = "transactionManager",
        basePackages = {"com.funsonli.springbootdemo153jpaatomikos.dao.user"})

public class UserConfig {
    @Autowired
    private JpaVendorAdapter jpaVendorAdapter;

    //user库
    @Primary
    @Bean(name = "userDataSourceProperties")
    @Qualifier("userDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.user")
    public DataSourceProperties userDataSourceProperties() {
        return new DataSourceProperties();
    }


    @Primary
    @Bean(name = "userDataSource", initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.user")
    public DataSource userDataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(userDataSourceProperties().getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(userDataSourceProperties().getPassword());
        mysqlXaDataSource.setUser(userDataSourceProperties().getUsername());
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("xadatabase1");
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setMaxPoolSize(20);
        return xaDataSource;

    }

    @Primary
    @Bean(name = "userEntityManager")
    @DependsOn("transactionManager")
    public LocalContainerEntityManagerFactoryBean userEntityManager() throws Throwable {

        HashMap properties = new HashMap();
        properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
        properties.put("javax.persistence.transactionType", "JTA");
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setJtaDataSource(userDataSource());
        entityManager.setJpaVendorAdapter(jpaVendorAdapter);
        entityManager.setPackagesToScan("com.funsonli.springbootdemo153jpaatomikos.entity.user");
        entityManager.setPersistenceUnitName("userPersistenceUnit");
        entityManager.setJpaPropertyMap(properties);
        return entityManager;
    }
}

OrderConfig 订单表所在数据源配置

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(
        entityManagerFactoryRef = "orderEntityManager",
        transactionManagerRef = "transactionManager",
        basePackages = {"com.funsonli.springbootdemo153jpaatomikos.dao.order"})
public class OrderConfig {

    @Autowired
    private JpaVendorAdapter jpaVendorAdapter;

    @Bean(name = "orderDataSourceProperties")
    @Qualifier("orderDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.order")
    public DataSourceProperties orderDataSourceProperties() {
        return new DataSourceProperties();
    }


    @Bean(name = "orderDataSource", initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.order")
    public DataSource orderDataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(orderDataSourceProperties().getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(orderDataSourceProperties().getPassword());
        mysqlXaDataSource.setUser(orderDataSourceProperties().getUsername());
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("xadatabase2");
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setMaxPoolSize(20);
        return xaDataSource;
    }

    @Bean(name = "orderEntityManager")
    @DependsOn("transactionManager")
    public LocalContainerEntityManagerFactoryBean orderEntityManager() throws Throwable {
        HashMap properties = new HashMap();
        properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
        properties.put("javax.persistence.transactionType", "JTA");
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setJtaDataSource(orderDataSource());
        entityManager.setJpaVendorAdapter(jpaVendorAdapter);
        entityManager.setPackagesToScan("com.funsonli.springbootdemo153jpaatomikos.entity.order");
        entityManager.setPersistenceUnitName("orderPersistenceUnit");
        entityManager.setJpaPropertyMap(properties);
        return entityManager;
    }

}

AddServiceImpl.java 服务层,方法需要加上@Transactional注解

@Service
public class AddServiceImpl implements AddService {
    @Autowired
    StudentDao studentDao;

    @Autowired
    OrderDao orderDao;

    @Override
    @Transactional
    public void add(String name) {
        Student student = new Student();
        String studentId = String.valueOf(SnowFlake.getInstance().nextId());
        student.setId(studentId);
        student.setName(name);
        student.setAge(18);
        studentDao.save(student);

        if ("0".equals(name)) {
            int k = 10 / Integer.valueOf(name);
        }

        Order order = new Order();
        order.setStudentId(studentId);
        order.setId(String.valueOf(SnowFlake.getInstance().nextId()));
        order.setSn(UUID.randomUUID().toString().replace("-", ""));
        order.setAmount(0);
        orderDao.save(order);
    }
}

StudentController.java 控制器,调用service操作数据库

@Slf4j
@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    AddService addService;

    @GetMapping({"", "/", "index"})
    public String index() {
        return "index";
    }

    @GetMapping("/add/{name}/{age}")
    public String add(HttpServletRequest request, @PathVariable String name, @PathVariable Integer age) {
        addService.add(name);
        return "ok";
    }
}


运行

点击运行

浏览器访问 http://localhost:8080/student/add/1/30
两边的表都可以生成数据

浏览器访问 http://localhost:8080/student/add/0/30
数据表user中没有数据,表示遇到异常,会进行跨数据库回滚

参考

  • Spring Boot入门样例源代码地址 https://github.com/funsonli/spring-boot-demo
  • Bootan源代码地址 https://github.com/funsonli/bootan

如果您喜欢本Spring Boot入门样例和样例代码,请点赞Star

你可能感兴趣的:(Spring,Boot入门样例)