Spring 启动-管理交易和多个数据源

标签 spring transactions spring-boot spring-jdbc

我试图将Spring Boot指南中的Managing Transactions示例扩展到两个数据源,但是@Transaction注释似乎仅对其中一个数据源有效。

在“Application.java”中,我为两个数据源及其JdbcTemplates添加了bean。在“BookingService.java”中,我使用了属于第二个数据源的JdbcTemplate。

这是我的“Application.java”:

package hello;

import javax.sql.DataSource;

import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

@SpringBootApplication
public class Application {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

    @Bean
    BookingService bookingService() {
        return new BookingService();
    }

    @Primary
    @Bean(name="datasource1")
    @ConfigurationProperties(prefix="datasource1")
    DataSource datasource1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name="jdbcTemplate1")
    @Autowired
    JdbcTemplate jdbcTemplate1(@Qualifier ("datasource1") DataSource datasource) {
        return new JdbcTemplate(datasource);
    }

    @Bean(name="datasource2")
    @ConfigurationProperties(prefix="datasource2")
    DataSource datasource2() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name="jdbcTemplate2")
    @Autowired
    JdbcTemplate jdbcTemplate2(@Qualifier ("datasource2") DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        log.info("Creating tables");
        jdbcTemplate.execute("drop table BOOKINGS if exists");
        jdbcTemplate.execute("create table BOOKINGS("
                + "ID serial, FIRST_NAME varchar(5) NOT NULL)");
        return jdbcTemplate;
    }

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        BookingService bookingService = ctx.getBean(BookingService.class);
        bookingService.book("Alice", "Bob", "Carol");
        Assert.assertEquals("First booking should work with no problem", 3,
                bookingService.findAllBookings().size());

        try {
            bookingService.book("Chris", "Samuel");
        }
        catch (RuntimeException e) {
            log.info("v--- The following exception is expect because 'Samuel' is too big for the DB ---v");
            log.error(e.getMessage());
        }

        for (String person : bookingService.findAllBookings()) {
            log.info("So far, " + person + " is booked.");
        }
        log.info("You shouldn't see Chris or Samuel. Samuel violated DB constraints, and Chris was rolled back in the same TX");
        Assert.assertEquals("'Samuel' should have triggered a rollback", 3,
                bookingService.findAllBookings().size());

        try {
            bookingService.book("Buddy", null);
        }
        catch (RuntimeException e) {
            log.info("v--- The following exception is expect because null is not valid for the DB ---v");
            log.error(e.getMessage());
        }

        for (String person : bookingService.findAllBookings()) {
            log.info("So far, " + person + " is booked.");
        }
        log.info("You shouldn't see Buddy or null. null violated DB constraints, and Buddy was rolled back in the same TX");
        Assert.assertEquals("'null' should have triggered a rollback", 3, bookingService
                .findAllBookings().size());
    }
}

这是“BookingService.java”:
package hello;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Transactional;

public class BookingService {

    private final static Logger log = LoggerFactory.getLogger(BookingService.class);

    @Autowired
    @Qualifier("jdbcTemplate2")
    JdbcTemplate jdbcTemplate;

    @Transactional
    public void book(String... persons) {
        for (String person : persons) {
            log.info("Booking " + person + " in a seat...");
            jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
        }
    };

    public List<String> findAllBookings() {
        return jdbcTemplate.query("select FIRST_NAME from BOOKINGS", new RowMapper<String>() {
            @Override
            public String mapRow(ResultSet rs, int rowNum) throws SQLException {
                return rs.getString("FIRST_NAME");
            }
        });
    }
}

这些是“application.yml”中的应用属性:
datasource1:
    url:        "jdbc:h2:~/h2/ds1;DB_CLOSE_ON_EXIT=FALSE"
    username:   "sa"

datasource2:
    url:        "jdbc:h2:~/h2/ds2;DB_CLOSE_ON_EXIT=FALSE"
    username:   "sa"

这里的“pom.xml”与Managing Transactions中的相同。

当@Primary批注位于datasource2 bean上时,一切都会按预期进行。
当@Primary批注位于datasource1 bean上时,datasource2中的写入不是事务性的,并且将获得以下输出:
...

2016-05-27 16:01:23.775  INFO 884 --- [           main] hello.Application                        : So far, Alice is booked.
2016-05-27 16:01:23.775  INFO 884 --- [           main] hello.Application                        : So far, Bob is booked.
2016-05-27 16:01:23.775  INFO 884 --- [           main] hello.Application                        : So far, Carol is booked.
2016-05-27 16:01:23.775  INFO 884 --- [           main] hello.Application                        : So far, Chris is booked.
2016-05-27 16:01:23.775  INFO 884 --- [           main] hello.Application                        : You shouldn't see Chris or Samuel. Samuel violated DB constraints, and Chris was rolled back in the same TX
Exception in thread "main" 2016-05-27 16:01:23.776  INFO 884 --- [       Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3901d134: startup date [Fri May 27 16:01:22 CEST 2016]; root of context hierarchy
java.lang.AssertionError: 'Samuel' should have triggered a rollback expected:<3> but was:<4>
    at org.junit.Assert.fail(Assert.java:88)
    at org.junit.Assert.failNotEquals(Assert.java:834)
    at org.junit.Assert.assertEquals(Assert.java:645)
    at hello.Application.main(Application.java:84)
2016-05-27 16:01:23.778  INFO 884 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

因此“克里斯”并没有退缩。

我想这与正确初始化两个数据库有关。这是一个错误,还是我在这里错过了一些东西?

谢谢!

最佳答案

我在“Application.java”中添加了两个bean:

@Bean(name="tm1") 
@Autowired
DataSourceTransactionManager tm1(@Qualifier ("datasource1") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

@Bean(name="tm2") 
@Autowired
DataSourceTransactionManager tm2(@Qualifier ("datasource2") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

并将“BookingService.java”中的@Transactional更改为:
@Transactional("tm2")

因此,现在我们有两个本地资源事务管理器,每个数据源一个,并且按预期工作。

非常感谢M.Deinum!

关于 Spring 启动-管理交易和多个数据源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37486361/

相关文章:

java - OneToMany 关系中的复合唯一约束

c# - 不同数据库的事务

java - Spring-Hibernate 应用程序中的 Whitelabel 错误页面

java - Spring Integration RabbitTemplate 默认情况下是否发布到持久队列?

spring - 使用 @EnableAutoConfiguration 在 Spring Boot 应用程序中自定义事务管理

java - Spring boot 2.0.5.RELEASE 与 Jersey 客户端 2.27 需要 java.naming.factory.initial

spring-boot - 如何使用 Spring Boot 和 Cassandra 将枚举持久化为序数?

spring - 如何使用 Spring Data Elasticsearch ElasticsearchRepository 突出显示

java - 让一个@Configuration 类定义另一个@Configuration 类有什么用?

entity-framework-4 - Entity Framework 中的多个SaveChanges调用