Hibernate Envers : track revisions in the owning side of a OneToMany relation - hibernate-envers

I have two audited entities, A and B. Entity A holds a collection of entity B (annotated as One-to-many relationship). When inserting a new instance of A into the database, all rows of A and B are at the same revision (let's say revision 1). Then, there is an update on A which only affect the instances of entity B (cascade type is merge). So after the update, the entity A is still at revision 1, whereas the entities of B are at revision 2 (new MOD entry in the audit table).
The problem is when I retrieve all the revisions of A, I would expect to get 2 revisions in return : one for the creation, one for the modification of the owning collection of B.
I can get this behaviour in case of ManyToMany but I can't get it work the same way with a OneToMany relation.
(I'm using Hibernate 3.6.10-Final)

I solved my problem by adding a hidden lastUpdated date field on my equivalent of your A entity.
#Entity
public class A {
private Date lastModified;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL )
private List<B> blist;
public void touch(){
lastModified=new Date();
}
}
In the related entities (like you B field), I added the following :
public class B {
#ManyToOne
private A a;
#PreUpdate
public void ensureParentUpdated(){
if(a!=null){
a.touch();
}
}
}
This ensures that a revision is added to A whenever a revision is added to B.

Related

JPA Cascade remove orphans

I have a question. I use JPA with eclipselink and I have relationship:
Table A ---< Table AB >---- Table B
class A {
#OneToMany(mappedBy = "a")
private List<AB> abList
}
class AB {
#ManyToOne(fetch = FetchType.LAZY)
private A a;
#ManyToOne(fetch = FetchType.LAZY)
private B b;
}
class B {
#OneToMany(mappedBy = "b")
private List<AB> abList;
}
It is ManyToMany relationship between A and B with join table AB.
Now I want to remove a record from table A and to cascade remove records from table AB(join table) and from B table as well.
But from B only those which arent't connected to any other record from table A (many-to-many relationship between A and B).
How should I set CascadeType or orphanremoval to do it properly?
If you want to remove AB and B when A is deleted then just set cascade remove, (and orphanRemoval=true if you want removed instances to be removed).
If the relationship from A to B is truly ManyToMany, i.e. B can have multiple references, then you cannot cascade the remove to B, (but can still cascade to AB, ensure you maintain bi-directional relationships in your model).
There is no way to delete B if there happens to be no relationship to it from anywhere. This would be something like garbage collection, which does not exist in relational databases. You need to handle this from your application. If B is something you want to delete with A, then consider not having it shared, but have each A have its own private B instance.
I suppose you are working with Hibernate
you should make an orphanRoval=true,
after that try this:
supposing we needto remove a (which class is A),
we make :
for (AB ab : a.getAbList()) {
B b = ab.get();
if (b.getAbList().size()==1) {
em.remove(b);
}
em.remove(ab);
}
em.remove(a);
Hope this help

How do I properly annotate two JPA entities which are in a parent child relationship?

Maybe this is a question with an easy answer ... but I don't get it running. At persist() I get the exception that the referential key in the child table is null (which of course is not allowed by the database). I have a recipe and some steps for preparation.
I'm using EclipseLink 2.4.1
Recipe.java (rcpid is autoset by JPA)
#Entity
public class Recipe {
#Id
long rcpid;
List<Recipestep> recipesteps = new ArrayList<>();
#OneToMany(
cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="recipe",
targetEntity=Recipestep.class )
// This does NOT work. Following line tries to access a join-table !!!
// #JoinColumn(name="rcpid", referencedColumnName="rcpid")
public List<Recipestep> getRecipesteps() { return recipesteps; }
// some more attributes, getters and setters
}
Recipestep.java (rpsid is autoset by JPA)
#Entity
public class Recipestep {
#Id
long rpsid;
Recipe recipe;
#ManyToOne( targetEntity=Recipe.class )
#JoinColumn( name="rcpid" )
public Recipe getRecipe() { return recipe; }
// some more attributes, getters and setters
}
The code above is a valid workaround. However to have clean (and supportable) code, the relationship should be only one-way with a collection in the parent which references all its children.
You have mapped this as a unidirectional one to many, but have two mappings for the recipestep rcpid database column. Try changing the long rcpid to
#ManyTOne
Recipe rcp;
And then remove the joincolumn definition from the oneToMany and make it bidirectional by marking it as mappedby the rcp manyToOne relation. An example is posted here http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Relationship_Mappings/Collection_Mappings/OneToMany
Eclipselink will always insert nulls on unidirectional oneToMany relations using a joincolumn when first inserting the target entity, and then update it later when it processes the Recipe entity. Your rcpid mapping in Recipestep is also likely null, which means you have two write able mappings for the same field which is bad especially when they conflict like this.
You are experiencing the default JPA behaviour. Adding an entity to the recipesteps list is not sufficient to create a bidirectional relation.
To solve the issue you need to set the rcpid explicitly on every element in the list.
EDIT: I think the issue is that JPA does not know where to store the id of the Recipe in the Recipestep table. It assumes a name ("recipebo_rcpid"), but your table seems to lack it.
Try adding the column "recipe_id" to the Recipestep table and a mappedBy attribute to the #OneToMany annotation:
#OneToMany(
cascade=CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = "recipe" )
You probably do not need the targetEntity attribute in the annotation- the List is typed already.

Correct way to statelessly update a one-to-many relationship in JPA?

I have a REST interface for a datamodel that has several one-to-many and many-to-many relationships between entities. While many-to-many relationships seem easy to manage statelessly, I'm having trouble with one-to-many. Consider the following one-to-many relationship:
Employee:
#ManyToOne
#JoinColumn(name = "Company_id")
private Company company;
Company:
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval=true)
public Set<Employee> employees = new HashSet<Employee>();
When a company is updated, its employee collection may have been updated as well (employees removed or added) but since the REST interface only allows updating the company as a whole, I cannot explicitly delete or add employees.
Simply replacing the collection does not work, but I found that this seems to work:
public void setEmployees(Set<Employee> employee) {
this.employees.clear(); // magic happens here?
this.employees.addAll(employees);
for (Iterator<Employee> iterator = employees.iterator(); iterator.hasNext();) {
Employee employee = (Employee) iterator.next();
employee.setCompany(this);
}
}
Is this the way it should be done, or is there a better way?
EDIT: In fact the above does not work! It appears to work at first, but then it will break with:
Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
I assume this happens because the db already contains a set of employees and if any of the "old" employees are also part of the replacement set, they collide with the ones in the database.
So what is the right way to replace the set?
First make sure equals is implemented properly. As per hibernate spec: http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode
I had a similar problem doing a merge. Essentially I had to fetch the existing employees associated with the company. I had to merge any changes to existing employees, and then add any new employees.
Query query = em.createQuery("select e from Employee e where e.company = '" + company.getId() + "'");
Collection<Employee> existingEmployees = new LinkedList<Employee>();
try{
Iterables.addAll(existingEmployees, (Collection<Employee>) query.getResultList());
}
catch(NoResultException nre){
//No results
}
for(Employee existingEmployee : existingEmployees){
for(Employee employee : company.getEmployees()){
if(existingEmployee.name().equals(employee.name())){
employee.setId(existingEmployee.getId());
}
employee.setCompany(company);
}
}
i think you have no better choice then to replace the existing collection and simply set the new one provided by the REST response.

Eclipselink OneToMany Merge Deletes Relations

I have a parent object Compound with a one-to-many relationship to an object Submission. I have set up the relationship as follows:
#Entity
public class Compound implements Serializable {
#Id
private long compoundId;
...
#OneToMany(mappedBy="compound", fetch=FetchType.LAZY,
cascade={ CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE })
#PrivateOwned
private List<Submission> submissions;
...
}
#Entity
public class Submission implements Serializable {
#Id
private long submissionId;
...
#ManyToOne(fetch=FetchType.LAZY,
cascade={ CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE })
#JoinColumn(name="compoundId")
private Compound compound;
...
}
I have set-up a form (in Flex) to modify a selected Compound. Given the form output (a BlazeDS class called ObjectProxy, which extends HashMap) I map the values from the form onto a new Compound object. The submissions data is not stored in the form hence the submissions list on the new Compound object remains null.
public Compound decodeWebForm(final ObjectProxy proxy) {
// create a new compound object
final Compound c = new Compound();
// map the proxy values onto the new compound
c.setCompoundId(proxy.get("compoundId"));
...
return updateCompound(c);
}
Next I merge the Compound object I have constructed into the persistence context.
public Compound updateCompound(final Compound c) {
// retrieve the entity manager
final EntityManager em = getEntityManager();
// begin a transaction
em.getTransaction().begin();
// merge changes to the managed compound
final Compound managed = em.merge(c);
// commit changes to the database
em.getTransaction().commit();
return managed;
}
I have specifically omitted the CascadeType.MERGE directive from the relationship definitions. As such, when I call the EntityManager.merge(Compound) method I was expecting the lack of submissions data to be ignored. However, this is not the case and any submissions relating to the Compound I am modifying are deleted.
I must be doing something wrong or have misunderstood the meaning of CascadeType.MERGE? Can someone please help?
Thanks
James
Two problems:
1) You marked the relationship with #PrivateOwned which should cause all dereferenced entities to also be deleted.
2) you are merging an empty collection. Cascade merge (or the lack there of) only means that the merge will (or will not be) cascaded to the Submissions entities. The state of the collection itself though is considered part of the Compound and so needs to be updated in the database. Coupled with #1, this means that Submissions will be deleted. IF not for #1, there would be no change in the database (since Submissions own the relationhip and haven't changed), but when reading back the Compound entity, it will show a null or empty collection unless it gets refreshed.
Creating a new instance of the entity is a bad idea. Instead, you might want to read it in and only change what is needed from the form - then merge this entity into the transactional context if required.

Entity Framework - Clear a Child Collection

I have run into an interesting problem with Entity Framework and based on the code I had to use to tackle it I suspect my solution is less than ideal. I have a 1-to-Many relationship between Table A and Table B where entities in TableB have a reference to TableA. I have a scenario where I want to simultaneously delete all children of a row in TableA and I thought this could be achieve by simply clearing the collection:
Entity.Children.Clear()
Unfortunately, when I attempted to save changes this produced as a Foreign Key violation.
A relationship is being added or
deleted from an AssociationSet
'FK_EntityB_EntityA'. With cardinality
constraints, a corresponding 'EntityB'
must also be added or deleted.
The solution I came up with was to manually delete object via the entity context's DeleteObject(), but I just know this logic I am using has got to be wrong.
while (collection.Any())
Entities.DeleteObject(collection.First());
For one, the fact that I had to use a Where() loop seems far less than ideal, but I suppose that's purely a semantic assessment on my part. In any case, is there something wrong with how I am doing this, or is there perhaps a better way to clear a child entity collection of an entity such that Entity Framework properly calls a data store delete on all of the removed objects?
Clear() removes the reference to the entity, not the entity itself.
If you intend this to be always the same operation, you could handle AssociationChanged:
Entity.Children.AssociationChanged +=
new CollectionChangeEventHandler(EntityChildrenChanged);
Entity.Children.Clear();
private void EntityChildrenChanged(object sender,
CollectionChangeEventArgs e)
{
// Check for a related reference being removed.
if (e.Action == CollectionChangeAction.Remove)
{
Context.DeleteObject(e.Element);
}
}
You can build this in to your entity using a partial class.
You can create Identifying relationship between parent and child entities and EF will delete child entity when you delete it from parent's collection.
public class Parent
{
public int ParentId {get;set;}
public ICollection<Child> Children {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public int ParentId {get;set;}
}
Mapping configuration:
modelBuilder.Entity<Child>().HasKey(x => new { x.ChildId, x.ParentId });
modelBuilder.Entity<Parent>().HasMany(x => x.Children).WithRequired().HasForeignKey(x => x.ParentId);
Trick: When setting up the relationship between Parent and Child, you'll HAVE TO create a "composite" key on the child. This way, when you tell the Parent to delete 1 or all of its children, the related records will actually be deleted from the database.
To configure composite key using Fluent API:
modelBuilder.Entity<Child>().HasKey(t => new { t.ParentId, t.ChildId });
Then, to delete the related children:
var parent = _context.Parents.SingleOrDefault(p => p.ParentId == parentId);
var childToRemove = parent.Children.First(); // Change the logic
parent.Children.Remove(childToRemove);
// you can delete all children if you want
// parent.Children.Clear();
_context.SaveChanges();
Done!