代码之家  ›  专栏  ›  技术社区  ›  Shorn

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

  •  2
  • Shorn  · 技术社区  · 6 年前

    使用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();
        }
      }
    
    }
    

    事务处理bean:

    @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()
        );
      }
    }
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   M. Deinum    6 年前

    使用 TransactionTemplate 来包装事务部分。弹簧靴提供了一个现成的,所以它可以随时使用。你可以使用 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