1

I would like to rollback a transaction for the data in case of errors and at the same time write the error to db.

I can't manage to do with Transactional Annotations.

Following code produces a runtime-error (1/0) and still writes the data into the db. And also writes the data into the error table.

I tried several variations and followed similar questions in StackOverflow but I didn't succeed to do.

Anyone has a hint, how to do?

@Service
public class MyService{

       @Transactional(rollbackFor = Exception.class)
        public void updateData() {
            try{
                processAndPersist();    // <- db operation with inserts
                int i = 1/0; // <- Runtime error
            }catch (Exception e){
                persistError()
                trackReportError(filename, e.getMessage());
            }
        }
    
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void persistError(String message) {
            persistError2Db(message); // <- db operation with insert
        }
mcfly soft
  • 11,289
  • 26
  • 98
  • 202

1 Answers1

0

You need the way to throw an exception in updateData() method to rollback a transaction. And you need to not rollback persistError() transaction at the same time.

@Transactional(rollbackFor = Exception.class)
        public void updateData() {
            try{
                processAndPersist();    // <- db operation with inserts
                int i = 1/0; // <- Runtime error
            }catch (Exception e){
                persistError()
                trackReportError(filename, e.getMessage());
                throw ex; // if throw error here, will not work

            }
        }

Just throwing an error will not help because persistError() will have the same transaction as updateData() has. Because persistError() is called using this reference, not a reference to a proxy.

Options to solve

  1. Using self reference.
  2. Using self injection Spring self injection for transactions
  3. Move the call of persistError() outside updateData() (and transaction). Remove @Transactional from persistError() (it will not work) and use transaction of Repository in persistError2Db().
  4. Move persistError() to a separate serface. It will be called using a proxy in this case.
  5. Don't use declarative transactions (with @Transactional annotation). Use Programmatic transaction management to set transaction boundaries manually https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s06.html

Also keep in mind that persistError() can produce error too (and with high probability will do it).

Using self reference

You can use self reference to MyService to have a transaction, because you will be able to call not a method of MyServiceImpl, but a method of Spring proxy.

@Service
public class MyServiceImpl implements MyService {

    public void doWork(MyService self) {
        DataEntity data = loadData();

        try {
            self.updateData(data);
        } catch (Exception ex) {
            log.error("Error for dataId={}", data.getId(), ex);
            self.persistError("Error");
            trackReportError(filename, ex);
        }
    }

    @Transactional
    public void updateData(DataEntity data) {
        persist(data);    // <- db operation with inserts
    }

    @Transactional
    public void persistError(String message) {
        try {
            persistError2Db(message); // <- db operation with insert
        } catch (Exception ex) {
            log.error("Error for message={}", message, ex);
        }
    }
}

public interface MyService {

    void doWork(MyService self);
    
    void updateData(DataEntity data);

    void persistError(String message);

}

To use

MyService service = ...;
service.doWork(service);
v.ladynev
  • 19,275
  • 8
  • 46
  • 67
  • Thanks for helping. I tried exactly the way you described, but I got an "javax.persistence.TransactionRequiredException: Executing an update/delete query" error. Is there no other simplier way todo without self referencing? For me this looks 'error-prone'. – mcfly soft Nov 23 '21 at 09:12
  • @mcflysoft Yo need to get the service from a Spring Context here `MyService service = ...`. Another way is to move persistError() in a separate service. Better not to call it form an origin transaction. – v.ladynev Nov 23 '21 at 10:26
  • @mcflysoft Where did you get that exception? – v.ladynev Nov 23 '21 at 10:27