The NullPointerException is because your mappings are not correct from an Envers perspective. You have two options: Join Table The relationship as you have it defined doesn't use a join table, but instead relies on placing the joining attributes inline on the @ManyToOne side. If you were to change the @OneToMany mapping not to use mappedBy, a join table would be used by Hibernate and Envers instead. When you use this approach, its worth noting that the tests as you have them coded will validate correctly, see below why I mention this. No Join Table In order to not use a join table, we'll need to leverage a few more Envers annotations and be explicit about the column join mappings. You're welcomed to look at this commit (https://github.com/Naros/hibernate-orm/commit/21f23fed81313af3853228173767d88ac83e7390) for reference. One important point to note is that the @OneToMany must be mapped as follows:
@OneToMany
@OrderColumn(name = "indexed_index")
@JoinColumn(name = "indexed_join_column")
@AuditMappedBy(mappedBy = "reference", positionMappedBy = "position")
private List<IndexedListRefIngEntity> referring = new ArrayList<>();
I removed the mappedBy from the @OneToMany because you want to allow the @AuditMappedBy to influence the mapping. If the mappedBy remained on the @OneToMany, the @AuditMappedBy annotation would have been ignored preventing Envers to know where the position is mapped for the @OrderColumn. But in order to not use a join-table and with mappedBy not being used on the @OneToMany, a @JoinColumn must be specified to explicitly map the relation join. On the @ManyToOne side, I needed to change its mapping and also add a new property:
@ManyToOne
@JoinColumn(name = "indexed_join_column", insertable = false, updatable = false)
private IndexedListRefEdEntity reference;
@Column(name = "indexed_index", insertable = false, updatable = false)
private Integer position;
Here you'll notice I added a @JoinColumn on the @ManyToOne for the same reasons as the @OneToMany. But Envers needs the collection-side to be the owner to manage proper auditing, and so the @JoinColumn is non-insertable and non-updatable. Additionally, I added a new property, position, that is a place holder for the @OrderColumn / @IndexColumn value specified by the @AuditMappedBy annotation on the opposite side of this relation. This field should be mapped as non-insertable and non-updatable to prevent user code from changing this value. It's essentially here to give Enver's visibility to this column, nothing more. It's worth noting that the column name given to the position property should match the name given to the @OrderColumn / @IndexColumn on the other side of the mapping. This is so that the right column is being read/maintained from both Envers and Hibernate for the collection index. The caveat here though is that Hibernate will generate a slightly different audit trail for this configuration. More specifically, you'll notice in the testRevisionsCounts() test, the last assertion is (1,2,3) rather than (1,3). This is due to the removal of an entity in the collection causing the remaining entity's index to change during Revision 2. With that said... I do think I'd like to investigate what would be necessary to improve the mapping necessary to build this type of an audit relationship. If possible, I'd prefer to see the mappings just as your test case originally had them and Envers being able to build it's internal model as necessary from it without any other annotations. I can't be certain what impacts that may have, specifically that Envers wants the ownership of the relation on the collection-side whereas your test case made that be the non-collection-side. I think what we need to do is at the very least here is to present the use with a MappingException while building the Envers model. This prevents this downstream NullPointerException and gives the user a bit of guidance on what may be wrong with their mappings. |