how to get OneToMany relationship revision in Hibe

2019-09-03 03:45发布

问题:

we have two entities like Resource and NonFTECost. And we have relationship between these is OneToMany bi-directional. below is the entities

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table
public class Resource {

    @NotNull
    @Column(name = "NAME", nullable = false, length = 255)
    @Audited(withModifiedFlag = true)
    private String name;

    @OneToMany(mappedBy = "resource", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    @Audited
    private List<NonFTECost> costings = new ArrayList<>();

    //other fields

}


@Getter
@Setter
@Table
@Entity
public class NonFTECost {

    @NotNull
    @Audited(withModifiedFlag = true, modifiedColumnName = "PAYMENT_MOD")
    private Payment payment;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "RESOURCE_CODE", nullable = false, foreignKey = @ForeignKey(name = FK_COST_DETAILS_RESOURCE_CODE))
    @Audited(withModifiedFlag = true, modifiedColumnName = "RESOURCE_CODE_MOD", targetAuditMode = RelationTargetAuditMode.AUDITED)
    private Resource resource;

    //other fields

}

now i create the one resource wiht costings, then it will create new revision for each audit table. And then i changed only payment field of NonFTECost entity, it will create new revision in NonFTECost_Aud table(it is also the part of resource update).

Question:- While getting the revision of resource, i want to get revisions of NonFTECost for that perticular resource entity. because of i want to show to user like fieldName oldvalue newvalue

Please help me to sort out the issue.

回答1:

You should be able to fetch the associated NonFTECost entities for a given revision of Resource by iterating that collection on the specific revision instance you queried.

For example, lets say I was interested in revision 5 of Resource

final Number revision = 5;

final AuditReader auditReader = auditReaderFactory.get( session );
final Resource resource = auditReader.find( Resource.class, resourceId, revision );
for ( Cost cost : resource.getCosts() ) {
  // do whatever with cost
}

Now what you need is how to take that Cost instance and find out what changed. Since you use the feature withModifiedFlags=true, this allows us to make special use of forRevisionsOfEntityWithChanges.

The one thing I want to point out is that it is possible in your mapping scenario that a NonFTECost entity could have a higher revision than that of your Resource if you happen to modify the cost entity in a transaction where no modification happens specifically to Resource .

With that in mind, you'll need to account for that in the for-loop logic. So inside that loop, we'll need to execute a query based on the Cost instance and fetch its revision history separately.

// This list is an object array that contains the following 
//   Index 0 - The `Cost` entity again at the revision
//   Index 1 - The revision entity (contains revision number/timestamp)
//   Index 2 - The RevisionType: ADD, MOD, or DEL
//   Index 3 - Set<String> of property names that changed at this revision
List results = auditReader.createQuery()
    .forRevisionsOfEntityWithChanges( Cost.class, false )
    .add( AuditEntity.id().eq( cost.getId() ) )
    .addOrder( AuditEntity.revisionNumber().desc() )
    .setMaxResult( 2 )
    .getResultList(); 

If the results list only contains 1 row, then you know a couple of things (assuming no data pruning)

  1. index 2 should be RevisionType.ADD
  2. Index 3 contains Set<String> of fields set during the original persist.
  3. There are no fields that have an old value, just whatever their insert persist value was.

If the results list contains 2 rows, then this is where you'll need to handle old and new value logic. You should be able to do something like this:

if ( results.size() == 2 ) {
  Object[] newArray = (Object[]) results.get( 0 );
  Object[] oldArray = (Object[]) results.get( 1 );
  Set<String> propertiesChanged = (Set<String>) newArray[3];
  for ( String propertyName : propertiesChanged ) {
    if ( "value".equals( propertyName ) ) {
      Double newValue = ( (NonFTECost) newArray[0] ).getValue();
      Double oldValue = ( (NonFTECost) oldArray[1] ).getValue();
    }
  }
}
else if ( results.size() == 1 ) {
  Object[] array = (Object[]) results.get( 0 );
  Set<String> propertiesChangedOnInsert = (Set<String>) array[3];
  // do whatever here
}

This isn't the most elegant of solutions, but it works.