0

I must perform an insert in a special global temporary table before the transaction commit.

This is due to an older design of the database that use this table to perform audit operations using triggers on every business table. Another software written in C use this pattern to perform audit with low impact on the code:

  • insert audit data in the temporary table when the connection is established
  • perform various insert/update, trigger will link these operation with the audit data
  • commit: the audit date are flushed from the temporary table

Now I have a Spring Boot + JPA (Hibernate) application that start to use the same database and I need to reproduce this pattern. However with the Spring + JPA abstraction of the transaction, I struggle to find a way to replicate this behavior.

I need to perform the insertion of the audit data when a transaction is created (or just before it is committed). I've checked this promising TransactionalEventListener, but it looks like I must declare a publisher and fire the event manually in each service, like in the following example:

@Service
public class CustomerService {
    private final CustomerRepository customerRepository;
    private final ApplicationEventPublisher applicationEventPublisher;
    public CustomerService(CustomerRepository customerRepository, ApplicationEventPublisher applicationEventPublisher) {
        this.customerRepository = customerRepository;
        this.applicationEventPublisher = applicationEventPublisher;
    }
    @Transactional
    public Customer createCustomer(String name, String email) {
        final Customer newCustomer = customerRepository.save(new Customer(name, email));
        final CustomerCreatedEvent event = new CustomerCreatedEvent(newCustomer);
        applicationEventPublisher.publishEvent(event);
        return newCustomer;
    }
}

As you can see in this sample, the service declare an ApplicationEventPublisher and need to call applicationEventPublisher.publishEvent(event); after each update/insert.

This is not fulfilling my needs: I really need to be able to do this operation before every commit() and this must be automatic for all services and repositories.

I've started to work on an AOP based solution, but I feel like its an overkill.

So is there any simple solution to perform some operation before any commit in a Spring Boot + JAP context?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Guillaume
  • 5,488
  • 11
  • 47
  • 83
  • have you tried with @EventListener(Customer.class) @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) – Ganesh Gudghe Nov 18 '19 at 12:02
  • 1
    Assuming you are using HIbernate write a hibernate eventlistener or interceptor and do it in there. Not to be confused with the Spring eventlisteners and interceptors or AOP. Or as a last resort you can always create a wrapper for the `PlatformTransactionManager` and do it in there before delegating to the actual transaction manager. – M. Deinum Nov 18 '19 at 12:08
  • @Ganesh the issue is that I want a completely automated system, not to bother with adding event publisher on all of my services. Do you have an example in mind that use TransactionalEventListener in such an automated way ? – Guillaume Nov 18 '19 at 12:58
  • @Deinum, thanks for the tip, the Hibernate interceptor looks like a good candidate, I'll try to figure out how to setup one for my project ! – Guillaume Nov 18 '19 at 13:01

1 Answers1

0

JPA provides you with event listener tool.

You can mark your entities with @EntityListener(YourListener.class) annotation:

@Entity
@EntityListeners(YourListener.class)
...
public class Entity implements Persistable<>

YourListener should be like:

public class YourListener<T extends Persistable> {
    @PostUpdate
    public void onPostPersist(T entity) {
        //do
    }
}

There are several annotations to mark methods of listener, you can choose following hooks: @(PrePersits/PreUpdate/PreRemove/PostPersits/PostUpdate/PostRemove/PostLoad).

Ensure you don't use the same EntityManager for any additional savings during executing listener! Please, see https://stackoverflow.com/a/42222592/5661496 and other answers there.

I personally ended up with starting new Hibernate session in case I need save data in another entity.

Nimtar
  • 188
  • 2
  • 11
  • The issue with this approach is that my custom pre-persist operation will be called once every time an entity is flushed. This could be an interesting work around but not the most efficient. – Guillaume Nov 18 '19 at 12:55