Search code examples
hibernatejpahibernate-envers

Mapping to an older revision of an entity with JPA annotations


I recently found out about envers for auditing and have been able to use it successfully to track revisions and fetch them using the @Audited annotation and the AuditReader. Now, what I'm trying to achieve is retaining the mappings to an audited entity at the revision they were made instead of the newest revision.

Quick example:

Let's say I have a recipe for cookies which I use to make batches of cookies (pseudo classes for classes below). Each recipe has a list of instructions to follow and doing so creates a batch:

@Audited
@Table(name="recipes")
class CookieRecipe {
    @OneToMany(mappedBy="recipe")
    private List<RecipeStep> steps;

    private void addStep(String instruction) {
        steps.add(new RecipeStep(instruction));
    }
}

@Table(name="batches")
class CookieBatch {
    @ManyToOne
    @JoinColumn(...)
    private CookieRecipe recipe;
}

@Audited
@Table(name="recipe_step")
class RecipeStep {

    @Column
    private String instruction;

    @ManyToOne
    @JoinColumn(...)
    private CookieRecipe recipe;

    private RecipeStep(String instruction) {
        this.instruction = instruction;
    }
}

Now, let's say I have this Cookie Recipe:

CookieRecipe recipe = new CookieRecipe();
recipe.addStep("Make the dough");
recipe.addStep("Place on pan");
recipe.addStep("Bake at 400F for 20 minutes");
entityManager.persist(recipe);

And I'll be using this recipe to create my first batch of cookies:

CookieBatch batch = new CookieBatch(recipe);
entityManager.persist(batch);

If I wanted to change the recipe to say, for example, 375F instead of 400F, this creates revision 2 of the CookieRecipe, which is what I expect and want. However, I want the batch I already created to point to revision 1 of the CookieRecipe. Currently, if I fetch the CookieBatch I've already created using its ID, the reference to the CookieRecipe ends up being the latest revision (the one with 375F).

Is this something I can accomplish using envers?


Solution

  • I believe your only way of doing this is keeping recipeId, recipeRevisionNumber fields in your CookieBatch, and loading a CookieRecipe object yourself.

    @Table(name="batches")
    class CookieBatch {
    
        @Column(...)
        Long recipeId;
    
        @Column(...)
        Long recipeRevisionNumber;
    
        @Transient
        private CookieRecipe recipe;
    
        @PostLoad
        public void loadRecipe()
        {
            // Load a cookie recipe via audit criteria
        }
    }
    

    audit criteria is pretty self explanatory, check out this example:

    Hibernate Envers get revisions for criteria

    and a documentation for all things envers:

    http://docs.jboss.org/envers/docs/