如何使用 JOOQ 和 Spring-boot 2.0 进行手动事务管理?

Posted

技术标签:

【中文标题】如何使用 JOOQ 和 Spring-boot 2.0 进行手动事务管理?【英文标题】:How to do manual transaction management with JOOQ and Spring-boot 2.0? 【发布时间】:2019-01-17 21:07:08 【问题描述】:

使用 Spring Boot 2.0.4 和 JOOQ 3.11.3。

我有一个服务器端点,需要对事务管理进行细粒度控制;它需要在外部调用之前和之后发出多个 SQL 语句,并且在与外部站点通信时不能保持 DB 事务处于打开状态。

在下面的代码中testTransactionV4 是我最喜欢的尝试。

我查看了 JOOQ 手册,但事务管理部分非常简单,似乎暗示这是这样做的方法。

感觉我比我应该在这里更努力地工作,这通常表明我做错了。有没有更好、“正确”的方式来使用 Spring/JOOQ 进行手动事务管理?

此外,对 TransactionBean 实现的任何改进都将不胜感激(并赞成)。

但这个问题的重点真的只是:“这是正确的方式吗?


测试端点:

@Role.SystemApi
@SystemApiEndpoint
public class TestEndpoint 
  private static Log log = to(TestEndpoint.class);

  @Autowired private DSLContext db;
  @Autowired private TransactionBean txBean;
  @Autowired private Tx tx;

  private void doNonTransactionalThing() 
    log.info("long running thing that should not be inside a transaction");
  

  /** Works; don't like the commitWithResult name but it'll do if there's
   no better way.  Implementation is ugly too.
   */
  @JsonPostMethod("testTransactionV4")
  public void testMultiTransactionWithTxBean() 
    log.info("start testMultiTransactionWithTxBean");

    AccountRecord account = txBean.commitWithResult( db ->
      db.fetchOne(ACCOUNT, ACCOUNT.ID.eq(1)) );

    doNonTransactionalThing();

    account.setName("test_tx+"+new Date());

    txBean.commit(db -> account.store() );
  

  /** Works; but it's ugly, especially having to work around lambda final
   requirements on references.   */
  @JsonPostMethod("testTransactionV3")
  public void testMultiTransactionWithJooqApi() 
    log.info("start testMultiTransactionWithJooqApi");

    AtomicReference<AccountRecord> account = new AtomicReference<>();
    db.transaction( config->
      account.set(DSL.using(config).fetchOne(ACCOUNT, ACCOUNT.ID.eq(1))) );

    doNonTransactionalThing();

    account.get().setName("test_tx+"+new Date());

    db.transaction(config->
      account.get().store();
    );
  

  /** Does not work, there's only one commit that spans over the long operation */
  @JsonPostMethod("testTransactionV1")
  @Transactional
  public void testIncorrectSingleTransactionWithMethodAnnotation() 
    log.info("start testIncorrectSingleTransactionWithMethodAnnotation");

    AccountRecord account = db.fetchOne(ACCOUNT, ACCOUNT.ID.eq(1));

    doNonTransactionalThing();

    account.setName("test_tx+"+new Date());
    account.store();
  

  /** Works, but I don't like defining my tx boundaries this way, readability
   is poor (relies on correct bean naming and even then is non-obvious) and is
   fragile in the face of refactoring.  When explicit TX boundaries are needed
   I want them getting in my face straight away.
   */
  @JsonPostMethod("testTransactionV2")
  public void testMultiTransactionWithNestedComponent() 
    log.info("start testTransactionWithComponentDelegation");

    AccountRecord account = tx.readAccount();

    doNonTransactionalThing();

    account.setName("test_tx+"+new Date());
    tx.writeAccount(account);
  

  @Component
  static class Tx 
    @Autowired private DSLContext db;

    @Transactional
    public AccountRecord readAccount() 
      return db.fetchOne(ACCOUNT, ACCOUNT.ID.eq(1));
    

    @Transactional
    public void writeAccount(AccountRecord account) 
      account.store();
    
  


TransactionBean:

@Component
public class TransactionBean 
  @Autowired private DSLContext db;

  /**
   Don't like the name, but can't figure out how to make it be just "commit".
   */
  public <T> T commitWithResult(Function<DSLContext, T> worker) 
    // Yuck, at the very least need an array or something as the holder.
    AtomicReference<T> result = new AtomicReference<>();
    db.transaction( config -> result.set(
      worker.apply(DSL.using(config))
    ));
    return result.get();
  

  public void commit(Consumer<DSLContext> worker) 
    db.transaction( config ->
      worker.accept(DSL.using(config))
    );
  

  public void commit(Runnable worker) 
    db.transaction( config ->
      worker.run()
    );
  

【问题讨论】:

只有我们自己弹出TransactionTemplate,而不是尝试实现您自己的。包装需要与之进行事务处理的部分。对于 JOOQ 来说,它似乎仍然是一个 Spring 托管事务,因此不需要更改。 【参考方案1】:

使用TransactionTemplate 包装事务部分。 Spring Boot 提供了一个开箱即用的功能,因此可以立即使用。您可以使用execute 方法将调用包装在事务中。

@Autowired
private TransactionTemplate transaction;

@JsonPostMethod("testTransactionV1")
public void testIncorrectSingleTransactionWithTransactionTemplate() 
  log.info("start testIncorrectSingleTransactionWithMethodAnnotation");

  AccountRecord account = transaction.execute( status -> db.fetchOne(ACCOUNT, ACCOUNT.ID.eq(1)));

  doNonTransactionalThing();

  transaction.execute(status -> 
    account.setName("test_tx+"+new Date());
    account.store();
    return null;
  

类似的东西应该可以解决问题。不确定 lambdas 是否可以工作(忘记TransactionCallback的语法

【讨论】:

以上是关于如何使用 JOOQ 和 Spring-boot 2.0 进行手动事务管理?的主要内容,如果未能解决你的问题,请参考以下文章

Jooq 无法找到 postgres 数据库的驱动程序

(JOOQ)当我尝试编译项目时,Mysql 出现连接错误

如何在 JOOQ 中使用 toChar 函数?

如何使用 jOOQ 通过 SQLite 正确生成日期和时间类型

Spring boot、JOOQ和Flyway如何一起使用?

如何使用 Gradle 和 Mysql 为单个数据库配置 jooq