Correct way to statelessly update a one-to-many relationship in JPA? - 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.

Related

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.

JPA and the last object added

I use EclipseLink JPA and for my work. And use GenerationType.TABLE.
When I create a new object using persist, how do I retrieve the id field of the created object.
Example:
em.getTransaction().begin();
Student student = new Student();
student.setName("Joe");
em.persist(student);
em.flush();
em.getTransaction().commit();
Query query = em.createQuery("SELECT e FROM Student e");
List<Student> list = (List<Student>) query.getResultList();
System.out.println(list);
em.close();
emf.close();
So I see all the students, but how do you know which one is the one that I created in an environment where there is competition.
Thanks in advance
I assume question is about generated id, because otherwise you of course already have id in your hands.
Generated id can be found after flush operation with following:
calling student.nameOfTheGetterForID
via PersistenceUnitUtil.getIdentifier:
//cast result to the type of id
em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(student);

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.

JPA OneToMany relations and performace

I have two entities: parent Customer and child Order.
Each Customer has 1,000,000 Orders for example, so it is not needed in any given time to load a Customer with all Orders but I want to have this ability to make join query on these two entities in JPA.
So because of this, I must create #OneToMany relationship for making join queries.
My question is: how to get query without making joinColumn because even in Lazy mode it is possible to load 1,000,000 objects!
I just want to get query on these object with where restrictions like native join.
If you don't want the #OneToMany relationship implicitly set in your Customer class than you don't have to. You can execute JPQL queries (in very precise manner) without the marked relationship.
Assume you have:
#Entity
public class Customer {
// all Customer-related fields WITHOUT #OneToMany relationship with Order
}
#Entity
public class Order {
#ManyToOne
private Customer owner;
}
Then if you want to get all Orders for particular Customer you can execute a simple JPQL query like that:
// Customer customer = ...
// EntityManager em = ...
String jpql = "SELECT o FROM Order o WHERE o.owner = :customer";
TypedQuery<Order> query = em.createQuery(jpql, Order.class);
query.setParameter("customer", customer);
List<Order> orders = query.getResultList();
In this way you can execute the code only when you're really sure you want to fetch Customer's orders.
I hope I've understood your problem correctly.
EclipseLink has support for QueryKeys, that allow you to define fields or relationships for querying that are not mapped. Currently there in no annotation support for query keys, but you can define them using the API and a DescriptorCustomizer.
Also you do not need the OneToMany to query on it, just use the inverse ManyToOne to query,
i.e.
Select distinct c from Customer c, Order o where o.customer = c and o.item = :item
Or,
Select distinct o.customer from Order o join o.customer c where o.customer = c and o.item = :item

Lazy loading doesn't work - jpa

Hy all,
I have three tables Child, Pet and Toy. Pet has a reference key the child id, and a toy has a reference key to a dog id.
I want to load all data about a Child and his pets, but i don't want to load the toy data
#OneToMany(mappedBy = "petEntity", fetch = FetchType.LAZY)
public Set<PetEntity> getPetEntitySet() {
return petEntitySet;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ChildId", insertable = false, updatable = false)
public ChildEntity getChildEntity() {
return childEntity;
}
same for set of toys.
to load data i have
List<ChildEntity> list = entityManager.createQuery(
"SELECT c FROM ChildEntity c "+
"LEFT JOIN FETCH c.petEntitySet ",
ChildEntity.class).getResultList();
return list;
but this thing loads me all data, not just the information about child and his pets.
How can i suppress the entity manager to load only the data in the table, and not to make joins when i don't want that
Thanks for your advices
i forgot to mention that in this chase it doesn't only load all data, but it returns more than just one copy of a child elemnt
The reason why it is loading more data is because you are using the keyword fetch. This will EAGERLY load whatever the child's pets.
Just remove the fetch and lazy loading will occur.
Update
I see that this is actually what you want, so just ensure equals() and hashCode() is correctly overriden in all relevant classes, as I assume you are using Set.
In this way, jpa knows how to look for duplicates
Second update
Yes you can easily use DISTINCT in your queries.
Just add DISTINCT in your select
List<ChildEntity> list = entityManager.createQuery(
"SELECT DISTINCT c FROM ChildEntity c "+
"LEFT JOIN FETCH c.petEntitySet ",
ChildEntity.class).getResultList();
return list;