如何使用Spring Tx同步事务?

huangapple go评论76阅读模式
英文:

How to synchronize transactions with Spring Tx?

问题

Here's the translated portion of your text:

我有三个不同的数据源,我想要发送信息到这些源:

  1. 一个与JpaRepository相关的Oracle数据库。
  2. 一个与MongoRepository相关的MongoDB。
  3. 一个通过yml配置了所有必要数据以生产到该主题的Kafka主题。

我想要使用Spring Tx同步这些资源。我有一个与Oracle数据库相关的JpaTransactionManager,一个与MongoDB相关的MongoTransactionManager,以及当启用事务时,Spring会自动创建的KafkaTransactionManager。

我心中的想法如下:

  1. 首先,我会有一个基本的控制器,它将自动装配一个服务:
@RestController
public class SimpleController {

  @Autowired
  private SimpleService simpleService;

  @PostMapping(...)
  public Response doTx () {
    simpleService.doTx();
  }
}
  1. 然后,该服务将同时自动装配另外三个服务,每个服务对应一个资源:
@Service
public class SimpleService {

  @Autowired
  private OracleService oracleService;

  @Autowired
  private MongoService mongoService;

  @Autowired
  private KafkaService kafkaService;
  
  ...
}
  1. 这样的SimpleService会有一个使用 @Transactional 注解标记的方法,在这个方法中调用这些服务:
@Service
public class SimpleService {

  @Transactional
  public void doTx () {

    oracleService.doTx();
    mongoService.doTx();
    kafkaService.doTx();

  }

}
  1. 并且,对这些方法的每个调用也会用 @Transactional 注解标记:
public class OracleService {

  @Autowired
  JpaRepository jpaRepository

  @Transactional
  public void doTx(){
    jpaRepository.save();
  }
}

但是这并不起作用。唯一“起作用”的方式是将所有资源放在一个服务中,都从同一个方法调用:

@Service
public class SimpleService {

  @Autowired
  private JpaRepository oracleJpaRepository;

  @Autowired
  private MongoRepository mongoRepository;

  @Autowired
  private KafkaTemplate kafkaTemplate;

  @Transactional(transactionManager = "oracleJpaTransactionManager")
  public void doTx() {
    oracleJpaRepository.save();
    mongoRepository.save();
    kafkaTemplate.send();
  }

所以问题基本上是,我的第一种方法是否有效(我认为它是有效的,因为它在Spring Kafka文档中的一些示例中包含在文档中),以及如何使其工作(不使用已弃用的ChainTransactionManager)。

任何帮助都将不胜感激 :D!

我尝试使用上述方法同步事务,但它只会在调用方法时提交,而不是加入之前的事务。

英文:

I have three different sources to which I want to send information:

  1. An Oracle database, related to a JpaRepository.
  2. A MongoDB, related to a MongoRepository.
  3. And a Kafka topic, which has configured via yml all the necessary data for producing into that topic.

I want to synchronize these resources with Spring Tx. I have a JpaTransactionManager related to the Oracle db, another MongoTransactionManager related to the MongoDB, and the KafkaTransactionManager created by Spring automatically when the transactions are activated.

The idea I had in mind was the following one:

  1. First, I would have a basic controller that would autowire a service:
@RestController
public class SimpleController {

  @Autowired
  private SimpleService simpleService;

  @PostMapping(...)
  public Response doTx () {
    simpleService.doTx();
}
  1. Then, that service would autowire at the same time another three services, one for each resource I have:
@Service
public class SimpleService {

  @Autowired
  private OracleService oracleService;

  @Autowired
  private MongoService mongoService;

  @Autowired
  private KafkaService kafkaService;
  
  ...
}
  1. Such SimpleService, would have a method anotated with the @Transactional annotation in which these services are called:
@Service
public class SimpleService {

  @Transactional
  public void doTx () {

    oracleService.doTx();
    mongoService.doTx();
    kafkaService.doTx();

  }

}
  1. And, each of these calls to these methods would be annotated with the @Transactional annotation too:
public class OracleService {

  @Autowired
  JpaRepository jpaRepository

  @Transactional
  public void doTx(){
    jpaRepository.save();
  }
}

What I would hope of this, is to initiate a transaction in SimpleService, and each of the other services that I call, join such transaction, and commit one after the other just when the method is about to return, at the end.

But this doesn't work. The only way it "works" (and I say "works" because it doesn't allow me to fully customize the order of committing the resources) is having all the resources in one service, all called from the same method:

@Service
public class SimpleService {

  @Autowired
  private JpaRepository oracleJpaRepository;

  @Autowired
  private MongoRepository mongoRepository;

  @Autowired
  private KafkaTemplate kafkaTemplate;

  @Transactional(transactionManager = "oracleJpaTransactionManager")
  public void doTx() {
    oracleJpaRepository.save();
    mongoRepository.save();
    kafkaTemplate.send();

  }

So the questions is, basically, if my first approach a valid approach (which I think it is since it appears at some Spring Kafka code samples included in the documentation) and how to make it work (without the ChainTransactionManager which is deprecated).

All help is appreciated :D!

I tried to synchronize transactions with the above approach, but it would just commit whenever the method was called, not joining the previous transaction.

EDIT: I'm not trying to achieve full transactionality. Just synchronization, a Best Effort 1 Phase Commit. I know there may be some data inconsistency when synchronizing these resources, compensation is not a problem, the key is the chaining of the commits at the end of the method.

EDIT 2:

Following Spring Kafka Documentation: https://docs.spring.io/spring-kafka/reference/html/#ex-jdbc-sync

@Transactional("dstm")
public void someMethod(String in) {
    this.jdbcTemplate.execute("insert into mytable (data) values ('" + in + "')");
    sendToKafka(in);
}

@Transactional("kafkaTransactionManager")
public void sendToKafka(String in) {
    this.kafkaTemplate.send("topic2", in.toUpperCase());
}

The following example shows how transactions are nested, and they do synchronize.

答案1

得分: 0

So, in short terms, transactional synchronization with more than one database and Kafka doesn't work.

Transactional synchronization at its full capacity looks like it will only work with a deprecated Spring Transaction class, ChainedTransactionManager, which does synchronize at the end of the method all operations made with Transaction Managers specified in such ChainedTransactionManager.

Nested calls to Transactional annotated methods won't synchronize.

Databases and Kafka will only be synchronized if only one database is put together with Kafka, as the comments to the question states it will break once a second database enters the game.

You can either use another pattern (Transactional Outbox sounds solid for these cases, but it comes with a higher temporal frame of data inconsistency), or copy-paste the ChainedTransactionManager class into your libraries/sample and play with it.

Note that the compensation once a commit fails will have to be done manually, since this is not XA or similar. So if you synchronize multiple databases and Kafka, be sure to catch the Heuristic exceptions and compensate properly.

If you understand the drawbacks of using the ChainedTransactionManager, it's an excellent tool to obtain such synchronization when your application needs to commit all resources together without business logic in between. Don't expect a Two-phase commit, this is a Best Effort 1 phase commit. So when a commit fails, all the commits made earlier won't roll back, you have to compensate manually.

英文:

So, in short terms, transactional synchronization with more than one database and kafka doesn't work.

Transactional synchronization at its full capacity looks like it will only work with a deprecated Spring Transaction class, ChainedTransactionManager, which does synchronize at the end of the method all operations made with Transaction Managers specified in such ChainedTransactionManager.

Nested calls to Transactional annotated methods won't synchronize.

Databases and Kafka will only be synchronized if only one database is put together with Kafka, as the comments to the question states it will break once a second database enters the game.

You can either use another pattern (Transactional Outbox sounds solid for these cases, but it comes with a higher temporal frame of data inconsistency), or copypaste the ChainedTransactionManager class into your libraries/sample and play with it.

Note that the compensation once a commit fails will have to be done manually, since this is not XA or similar. So if you synchronize multiple databases and kafka, be sure to catch the Heuristic exceptions and compensate properly.

If you understand the drawbacks of using the ChainedTransactionManager it's an excellent tool to obtain such synchronization when your application needs to commit all resources together without business logic in between. Don't expect a Two-phase commit, this is a Best Effort 1 phase commit. So when a commit fails, all the commits made earlier won't roll back, you have to compensate manually.

huangapple
  • 本文由 发表于 2023年6月12日 16:49:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76454963.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定