@ManyToMany and PostUpdateEventListener


#1

Hello

I got PostUpdateEventListener working very good for me, but it does not detect @ManyToMany collection update. I tried also other possibilities like checking PostCollectionUpdateEventListener, PreUpdateEventListener, PreCollectionUpdateEventListener with no success. Everywhere I got collection after update.

Collection code (owner):

	@ManyToMany(fetch = FetchType.LAZY)
	@JoinTable(name = "LEFT_TO_RIGHT_MAPPING", joinColumns = {
			@JoinColumn(name = "LEFT_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
					@JoinColumn(name = "RIGHT_ID", referencedColumnName = "ID") })
	private List<RightEntity> rightEntities;

right side:

	@ManyToMany(mappedBy = "rightEntities")
	private List<LeftEntity> leftEntities;

Saving using JpaRepository
Spring 4.3.9, Hibernate 4.3.11

Thanks,
Marek


#2

The PreCollectionUpdateEventListener works for unidirectional @OneToMany and embeddable collections only, not for @ManyToMany.

If you want to catch the @ManyToMany change events, take a look at the flushEverythingToExecutions method of the AbstractFlushingEventListener class.

Another alternative is to map the intermediary join table as explained in this article and then use plain entity events for the joined table entity.

Or, just use Envers.


#3

This is something i copied and rework from Hibernate source based on yours advice:

@Override
public void onPostUpdate(PostUpdateEvent event)
{
String[] propertyNames = event.getPersister().getPropertyNames();
List<String> dirtyPropertyNames = Arrays.stream(event.getDirtyProperties())
		.mapToObj((index) -> propertyNames[index])
		.collect(Collectors.toList());
logger.info("\n\nEntity {} update, dirty fields {}\n\n", event.getEntity().getClass().getSimpleName(), dirtyPropertyNames);

for (Map.Entry<PersistentCollection, CollectionEntry> me : IdentityMap
		.concurrentEntries((Map<PersistentCollection, CollectionEntry>) event.getSession().getPersistenceContext().getCollectionEntries()))
{
	PersistentCollection coll = me.getKey();
	CollectionEntry ce = me.getValue();
	
	if (event.getEntity() instanceof LeftEntity && ce.isDoupdate() && ce.getRole().endsWith(".rightEntities")){
		logger.info("\n====rightEntities changed==========");
		logger.info("\nNewCollection\n{}", coll);
		logger.info("\nOldCollection\n{}\n\n============", coll.getStoredSnapshot());
	}
}
}

I just consider to compare new against old collection manually to detect changes.
Is this a good start-point?

Marek


#4

It’s a good start. But you still didn’t say why you do it in the first place.

Maybe there’s a different way to solve the initial use case.


#5

I agree, i didn’t describe problem. Let me fix it :slightly_smiling_face:
In my DB I have a one audited entity. I need very limited change log. For every change I need to store only information of added, deleted or updated record. For update I need changed fields and date. No content/value info at all. Only for relationships I need information which entity was linked/unlinked and time. Additionally I need only information about the last change, so I do not care how many times a record/field was deleted. Final date and scope are important. The change log is valid up to next checkpoint, when changes are approved and the log is cleared.

I was considering envers, but this looks like overkill for me. Listeners was simplest solution, but ManyToMany problem appeared. Now it needs some work, but I still believe this can be easily solver. With envers there was tons of data, and building final change log seemed to be very complicated (for described above taks).

Thanks
Marek


#6

You don’t even need to do that. The DB already maintains all that info in the Redo Log. You can use Debezium to parse it and get all that info.


#7

Yes, that looks great, but have very limited database support. For some reasons we have to use some uncommon database (Apache Derby).