Question

In my application, I need to retrieve the lists of new, updated and removed entities per each transaction. Like this:

// useful functionality
@Transactional
public void createNewBlogPost(int userId, String title, String text) {
  Post post = new Post();
  post.title = title; // "hello"
  post.text = text; // "there"
  postRepository.save(post);
  // more work with JPA repositories here
}
...
// gets called right after createNewBlogPost()
public void onTransaction(UnitOfWork uow) {
  List<?> newEntities = uow.getNewEntities();
  assertEquals(1, newEntities.size()); // 1 new entity

  Object firstNewEntity = newEntities.get(0);
  assertTrue(firstNewEntity instanceof Post); // this new entity
                                              // is a Post

  Post newPost = (Post)firstNewEntity;
  assertEquals("hello", newPost.title);
  assertEquals("there", newPost.text);
}

The most relevant thing I managed to find was an audit functionality that Spring provides with annotations like @CreatedBy, @CreatedDate, @LastModifiedBy, @LastModifiedDate. Though it's technically very close, yet it's not exactly what I want to achieve.

Does Spring Data JPA provide a mechanism to retrieve data changes per every single transaction?

Was it helpful?

Solution 2

I've spent some time for the research and managed to find a relatively straightforward Hibernate-specific solution. There are basically 2 problems to resolve:

  1. Intercept data change events.
  2. Do it on a per-request basis.

To address p.1, one can use EventListenerRegistry. Here's an example:

@Component
public class HibernateListenersConfigurer {
    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Autowired
    private HibernateListeners hibernateListeners;

    @PostConstruct
    public void init() {
        HibernateEntityManagerFactory hibernateEntityManagerFactory =
                (HibernateEntityManagerFactory)entityManagerFactory;
        SessionFactoryImpl sessionFactoryImpl =
                (SessionFactoryImpl)hibernateEntityManagerFactory.getSessionFactory();
        EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.
                getServiceRegistry().
                getService(EventListenerRegistry.class);

        eventListenerRegistry.appendListeners(EventType.PRE_INSERT, hibernateListeners);
        eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, hibernateListeners);
        eventListenerRegistry.appendListeners(EventType.PRE_DELETE, hibernateListeners);
    }
}

hibernateListeners object gets all these events and can do whatever required to audit them. Here's an example:

@Component
public class HibernateListeners implements 
        PreInsertEventListener, 
        PreUpdateEventListener, 
        PreDeleteEventListener {

    @Autowired
    private ChangeTracker changeTracker;

    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        // event has a bunch of relevant details
        changeTracker.trackChange(event);    
        return false;
    }
    ...other listeners here...

Then, to address p.2, changeTracker seen above is a request-scoped bean:

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ChangeTracker {
    // a sort of "Unit of Work"
    private List<Change> changes = new ArrayList<Change>();

    public void trackChange(PreInsertEvent event) {
        changes.add(makeChangeFromEvent(event));
    }

    public void handleChanges() {
        // Do whatever needed :-)
    }
}

Then, there are few options available to finally call handleChanges() once request processing is complete: call it manually, use HandlerInterceptor, use filter, use AOP. HandlerInterceptors and filters, are not as great, because in my case they were getting called after response has already been sent to the client, this caused inconsistency between "business data" and "changes data". I eventually switched to AOP and it seems to work just fine.

Here's a playground: https://github.com/loki2302/spring-change-tracking-experiment

OTHER TIPS

Since your use case is Hibernate and JPA specific, you should take a look at Hibernate Envers and Spring Data Envers. They might give you some ideas, but be careful re: the projects themselves, I'm not sure if they're active.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top