spring boot - 管理事务和多个数据源
Posted
技术标签:
【中文标题】spring boot - 管理事务和多个数据源【英文标题】:spring boot - managing transactions & multiple datasources 【发布时间】:2016-09-25 23:07:29 【问题描述】:我尝试将 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
所以“克里斯”没有回滚。
我想这与正确初始化两个数据库有关。这是一个错误,还是我在这里遗漏了什么?
谢谢!
【问题讨论】:
您还需要多个事务管理器并指定@Transactional
使用 2 个 tx 管理器中的哪一个。
【参考方案1】:
我在“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!
【讨论】:
这个解决方案有两点我不喜欢,首先它没有使用 javax.transaction.Transactional。我知道这是一个弹簧应用程序,可能到处都是春天,但是如果这是我不喜欢的第二件事,我将我的服务分成一个以与 DI 无关的方式开发的单独工件,那么这个解决方案将失败完成目标。 它是否适用于两个以上的数据库?如果是这样,我们需要做任何额外的配置吗? 调查了2天。你的解决方案做到了。以上是关于spring boot - 管理事务和多个数据源的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JOOQ 和 Spring-boot 2.0 进行手动事务管理?
15《Spring Boot 入门教程》多数据源与分布式事务
Spring Boot 项目中配置多数据源@Transactional注解失效问题