Eclipselink OneToMany Merge Deletes Relations - jpa

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.

Related

Using JPA CompositeKey uses a property from another Entity

I tried searching but was having problems finding what I want. I have the following schema( * indicates primary key)
USER
*UserId
-Desc
Registration
*DeviceId
*UserId
-date
So I want to create a primary key for Registration like...
#Embeddable
public class RegPk{
private String deviceId;
private User user;
#Column(name="DEV_ID")
public String getDevId(){
return deviceId;
}
...
#ManyToOne
#JoinColumn(name="USER_ID", referencedColumnName="USER_ID")
public User getUser() {
return user;
}
...
}
Is this right? Will it update the USER_ID field properly update in registration when I persist?
When I try this kind of thing out I get the following....
[10/7/13 13:37:07:156 EDT] 000000ae webapp E com.ibm.ws.webcontainer.webapp.WebApp logServletError SRVE0293E: [Servlet Error]-[Hello]: org.apache.openjpa.util.MetaDataException: The id class specified by type "class org.me.mfa.jpa.Registration" does not match the primary key fields of the class. Make sure your identity class has the same primary keys as your persistent type, including pk field types. Mismatched property: "user"
So what now?
JPA does not allow primary key classes to contain relationships, only basic types. JPA 2.0 allows relationships to be apart of the ID, but you would move the relationship to the entity class, and have RegPk contain a deviceId and UserId. The Device-User relationship would then be marked with either #Id or #MapsId("user") depending on if you wanted to use a #PKClass or #EmbeddedId within your entity. See http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/DerivedIdentifiers for an example using a pk class.
In JPA 1.0, you would use a similar setup, except that your Device-User relationship would be marked read-only (either by specifying the #PrimaryKeyJoinColumn annotation, or by marking the #JoinColumn with insertable=false, updatable=false). You would then need to set the primary key basic mapping value manually, pulling the value from the referenced User entity directly. This of course requires that the id in User already be assigned, which might require additional work if both objects are new.
I would add cascade = CascadeType.ALL to #ManyToOne annotation to forward persist action for User entity.
It will look like:
...
#ManyToOne(optional=true, cascade = CascadeType.ALL)
#JoinColumn(name="USER_ID", referencedColumnName="USER_ID")
public User getUser() {
return user;
}
...
Well, if there is not other error, it could work. I'm little confused with optional=true, cuz it can provide id with deviceId,null but that could be OK also.

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.

JPA Collection of Primary Keys

I am looking to create a collection of primary keys (effectively a one-to-many relationship of entity keys without resolving the referenced entity).
For example,
#Entity
public class BigObject {
#EmbeddedId
private BigObjectId id;
// lots of other stuff
}
#Embeddable
public class BigObjectId {
//fields here
}
#Entity
public class Referrer {
// This won't work since BigObjectId is an embeddable. I would like a join table
// REFERRER_BIGOBJECTS with a REFERRER_ID PK foreign key and a BIGOBJECT_ID PK
// foreign key.
#OneToMany
private Set<BigObjectId> bigObjectIds;
}
I realize this seems to defeat the purpose of ORM, but it is beneficial to be able to iterate through the big objects without having to resolve them in their entirety (the embedded ID object is used other places in the system). Is there a a way to do this?
You should be able to use an ElementCollection mapping.
See,
http://en.wikibooks.org/wiki/Java_Persistence/ElementCollection#Basic_Collections

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!