springboot JTA最佳实践

2 阅读3分钟

以下是使用 PostgreSQL 数据库的完整配置,结合 MyBatis 和 JTA (Atomikos) 实现分布式事务。


1. 项目结构(与之前相同,仅调整数据库相关配置)

src/
├── main/
│   ├── java/
│   │   └── com/example/
│   │       ├── config/            # 配置类
│   │       │   ├── Db1Config.java
│   │       │   ├── Db2Config.java
│   │       ├── mapper/            # MyBatis Mapper接口
│   │       │   ├── db1/UserMapper.java
│   │       │   └── db2/OrderMapper.java
│   │       ├── model/             # 实体类
│   │       │   ├── User.java
│   │       │   └── Order.java
│   │       ├── service/           # 服务层
│   │       │   └── OrderService.java
│   │       └── Application.java   # 启动类
│   └── resources/
│       ├── mapper/                # MyBatis XML
│       │   ├── db1/UserMapper.xml
│       │   └── db2/OrderMapper.xml
│       └── application.yml        # 配置文件

2. 依赖配置(pom.xml)

替换 MySQL 驱动为 PostgreSQL 驱动

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- JTA (Atomikos) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jta-atomikos</artifactId>
    </dependency>
    
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    
    <!-- PostgreSQL 驱动 -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.6.0</version>
    </dependency>
</dependencies>

3. 数据源配置(application.yml)

调整数据库驱动和连接参数

spring:
  jta:
    enabled: true
    atomikos:
      properties:
        log-base-dir: ./transaction-logs
        default-timeout: 60s

  datasource:
    db1:
      xa-data-source-class-name: org.postgresql.xa.PGXADataSource  # PostgreSQL XA 驱动
      xa-properties:
        url: jdbc:postgresql://localhost:5432/db1
        user: postgres
        password: 123456
      unique-resource-name: db1
    db2:
      xa-data-source-class-name: org.postgresql.xa.PGXADataSource  # PostgreSQL XA 驱动
      xa-properties:
        url: jdbc:postgresql://localhost:5432/db2
        user: postgres
        password: 123456
      unique-resource-name: db2

4. MyBatis 多数据源配置类

(1) 主数据源配置(Db1Config.java)

@Configuration
@MapperScan(
    basePackages = "com.example.mapper.db1",
    sqlSessionFactoryRef = "sqlSessionFactoryDb1"
)
public class Db1Config {
​
    @Bean(name = "dataSourceDb1")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
    public DataSource dataSourceDb1() {
        return new AtomikosDataSourceBean();
    }
​
    @Bean(name = "sqlSessionFactoryDb1")
    public SqlSessionFactory sqlSessionFactoryDb1(
            @Qualifier("dataSourceDb1") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/db1/*.xml")
        );
        return factoryBean.getObject();
    }
}

(2) 次数据源配置(Db2Config.java)

@Configuration
@MapperScan(
    basePackages = "com.example.mapper.db2",
    sqlSessionFactoryRef = "sqlSessionFactoryDb2"
)
public class Db2Config {
​
    @Bean(name = "dataSourceDb2")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource dataSourceDb2() {
        return new AtomikosDataSourceBean();
    }
​
    @Bean(name = "sqlSessionFactoryDb2")
    public SqlSessionFactory sqlSessionFactoryDb2(
            @Qualifier("dataSourceDb2") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/db2/*.xml")
        );
        return factoryBean.getObject();
    }
}

5. MyBatis Mapper 接口与 XML

(1) UserMapper.java(db1 数据库)

package com.example.mapper.db1;
​
@Mapper
public interface UserMapper {
    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    void insertUser(User user);
}

(2) UserMapper.xml

<!-- src/main/resources/mapper/db1/UserMapper.xml -->
<mapper namespace="com.example.mapper.db1.UserMapper">
    <insert id="insertUser" parameterType="com.example.model.User">
        INSERT INTO users(name, email)
        VALUES(#{name}, #{email})
    </insert>
</mapper>

(3) OrderMapper.java(db2 数据库)

package com.example.mapper.db2;
​
@Mapper
public interface OrderMapper {
    @Insert("INSERT INTO orders(user_id, amount) VALUES(#{userId}, #{amount})")
    void insertOrder(Order order);
}

(4) OrderMapper.xml

<!-- src/main/resources/mapper/db2/OrderMapper.xml -->
<mapper namespace="com.example.mapper.db2.OrderMapper">
    <insert id="insertOrder" parameterType="com.example.model.Order">
        INSERT INTO orders(user_id, amount)
        VALUES(#{userId}, #{amount})
    </insert>
</mapper>

6. 服务层与事务管理(与 MySQL 版本一致)

@Service
public class OrderService {

    @Autowired
    private UserMapper userMapper;    // 操作 db1 的 Mapper

    @Autowired
    private OrderMapper orderMapper;  // 操作 db2 的 Mapper

    @Transactional  // 使用 JTA 全局事务
    public void createOrder(User user, Order order) {
        userMapper.insertUser(user);    // 插入 db1
        orderMapper.insertOrder(order); // 插入 db2
        // 若此处抛出异常,两个插入操作将同时回滚
    }

    // 测试事务回滚
    public void testRollback() {
        User user = new User("Alice", "alice@example.com");
        Order order = new Order(1L, 100.0);
        try {
            createOrder(user, order);
        } catch (Exception e) {
            System.out.println("事务已回滚");
        }
    }
}

7. 启动类(Application.java)

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

8. 测试与验证

  1. 创建 PostgreSQL 表

    -- db1.users
    CREATE TABLE users (
      id SERIAL PRIMARY KEY,
      name VARCHAR(50),
      email VARCHAR(50)
    );
    
    -- db2.orders
    CREATE TABLE orders (
      id SERIAL PRIMARY KEY,
      user_id INT,
      amount DECIMAL(10,2)
    );
    
  2. 运行测试方法

    @SpringBootTest
    public class OrderServiceTest {
        @Autowired
        private OrderService orderService;
    
        @Test
        public void testDistributedTransaction() {
            orderService.testRollback();
        }
    }
    
  3. 验证结果

    • createOrder() 方法无异常,两个数据库均插入数据。
    • 若方法内抛出异常,两个数据库均无数据插入。

关键调整点总结

  1. 依赖调整:替换 MySQL 驱动为 PostgreSQL 驱动。

  2. 数据源配置

    • xa-data-source-class-name: org.postgresql.xa.PGXADataSource
    • 数据库 URL 格式:jdbc:postgresql://host:port/database
  3. 事务管理器:无需修改,Spring Boot 自动配置 JtaTransactionManager

  4. SQL 语法:注意 PostgreSQL 与 MySQL 的差异(如自增主键 SERIAL)。

通过以上配置,MyBatis 和 JTA 可在 PostgreSQL 中实现分布式事务管理。