【发布时间】: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 托管事务,因此不需要更改。
标签: spring spring-boot java-8 jooq