Home > database >  Proper way to use multiple transactions in a single class
Proper way to use multiple transactions in a single class

Time:01-18

I'm using Spring Boot. I can use @Transactional to force transaction on a method. Sometimes I need for some method to use two or more transactions.

Naive approach wouldn't work:

public void doActions() {
    doAction1();
    doAction2();
}

@Transactional
void doAction1() { ... }

@Transactional
void doAction2() { ... }

because Spring uses proxies to implement transactions.

Usually I've used the following approach:

@Autowired
private ThisService self;

public void doActions() {
    self.doAction1();
    self.doAction2();
}

@Transactional
void doAction1() { ... }

@Transactional
void doAction2() { ... }

It worked, but in Spring 2.6.0 this circular dependency causes application to fail to start with scary error unless I set spring.main.allow-circular-references to true.

I don't really understand the reason why circular references are bad. But apparently Spring Boot developers want to discourage this kind of design, so, I guess, I better follow their advice.

Another approach is to use transaction manager and programmatically call transaction api:

@Autowired
private TransactionTemplate transactionTemplate;

public void doActions() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) {
                doAction1();
            }
        });
        transactionTemplate.execute(status -> {
            doAction2();
            return null;
        });

It works but it's a little bit verbose and ugly in my opinion.

Is there any other approaches I missed? Should I just set spring.main.allow-circular-references to true? I'm afraid that developers will make those circular references thing unsupported in the future and I'd need to rework my application because of that.

CodePudding user response:

Yes. I agree it is ugly. You can create a service that is just responsible to execute some codes in a transaction . Same idea as TransactionTemplate but it uses @Transational to manage the transaction.

@Service
public class TransactionExecutor {

    @Transactional(propagation = Propagation.REQUIRED)
    public <T> T execute(Supplier<T> action) {
        return action.get();
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void execute(Runnable action) {
        action.run();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public <T> T executeInNewTx(Supplier<T> action) {
        return action.get();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void executeInNewTx(Runnable action) {
        action.run();
    }

}

And then inject it to use it :

@Service
public class FooService {
    
    @Autowired
    private TransactionExecutor txExecutor;


    public void doActions() {
        txExecutor.execute(()->doAction1());
        txExecutor.execute(()->doAction2());
    }

    void doAction1() { ... }

    void doAction2() { ... }

}

CodePudding user response:

You can use in your methods the @Transactional(Transactional.TxType.REQUIRES_NEW), where right now you have the default configuration of @Transactional which is translated as @Transactional(Transactional.TxType.REQUIRED).

By using REQUIRES_NEW each method will create it's own transaction when starting and it will commit on method exit if successful or rollback if an error happens.

Also you will avoid the structure of circular reference if you replace private ThisService self; that you use with another layer of DAO. Just create another class that will represent the DAO layer and will have specific methods for specific actions in DB. Each method will have @Transactional(Transactional.TxType.REQUIRES_NEW) and those methods will be called by your existing service layer.

  •  Tags:  
  • Related