I guess I don't really understand bidirectional one-to-many relations.
I have a parent, a child-A and child-B class with bidirectional relations between Parent <-> Child-A <-> Child-B. I want to add a child-B to the child-B-list of the child-A, but on every try to merge the parent I get an dublicated key exception. So how do I add a new child-B.
class Parent {
long id;
#One-To-Many, MappedBy Parent, Cascade All, OrphanRemoval true
list <child-A> childsA;
...
setter + getter
}
and
class Child-A {
long id;
#Many-To-One, JoinColums parentId, Cascade PERSIT MERGE REFRESH
Parent parent;
#One-To-Many, MappedBy Child-A, Cascade All, OrphanRemoval true
list <child-B> childsB;
...
setter + getter
}
and
class Child-B {
long id;
#Many-To-One, JoinColums Child-A-id, Cascade PERSIT MERGE REFRESH
Child-A child-A;
...
setter + getter
}
How do I add a new child-B to child-A and merge the parent to save everything in the db? So far I've tried:
Parent p = entityManager.getParent();
Child-A ca = p.getChildsA.get(indexOfCa); // the index is known
Child-B cb = new Child-B ();
... // fill cb with information
ca.add(cb); // automatically fires cb.setChild-A(ca);
p.getChildsA.set(index, ca);
entityManager.merge(p);
But this causes a DublicatedKeyException. So what is the best practise to add a child-B object to an already persisted child-A-object from an parent-object?
I also have to say, only merge is possible (no save, saveOrUpdate or persist) and it is not possible to edit the entity-classes. The entities are generated by something like a factory and every change will be overwritten when building the project. And the pom-file is not editable.
Also this is a Java EE web-application with many different frameworks like primefaces and omnifaces.
Solved it by myself. The limit for auto generated primary keys on the mysql database where to low. So jpa run out of primary keys and tried to override existing keys. Increased the hibernate_sequence number and the exception was gone.
Related
Let´s assume these two entities:
#Entity
public class MyEntity {
#Id private String id;
#OneToMany(mappedBy = "myEntity", cascade = ALL) private Set<MyEntityPredecessor> predecessors;
}
#Entity
public class MyEntityPredecessor{
#Id private String id;
#ManyToOne(name = "entityID", nullable = false) private MyEntity myEntity;
#ManyToOne(name = "entityPre", nullable = false) private MyEntity predecessor;
}
When I try to call a delete with Spring Boot Data (JPA) with a MyEntity Instance, it will work some times (I see the select and then the delete statements in correct order), but sometimes it will try to run an update on the second entity trying to set the "entityPre" Field to null (even thoug it is set to nullable=falsE), causing the DB to send an error (null not allowed!! from DB constraint).
Strangely, this will happen at "random" calls to the delete...
I just call "myEntityRepository.getOne(id)", and then myEntityRepository.delete() with the result... There is no data difference in the DB between calls, the data structure has no null values when calling the delete method, so that should not be the reason.
Why is JPA sometimes trying to call updates on the Predecessor Table, and sometimes directly deleting the values? Am I missing something?
Add a similar ManyToOne annotated set to MyEntity which refers to the other non-nullable property, like:
#OneToMany(mappedBy = "predecessor", cascade = ALL) private Set<MyEntityPredecessor> other;
some explanation:
The issue doesn't happen randomly, but happen when you try to delete an entity which is linked to one (or more) MyEntityPredecessor via the predecessor property (which is mapped to the entityPre field)
Only the other field (entityID) is mapped back to the MyEntity object, so the deletion-cascade only happens via by that field.
So that's a question which it seems there's no official answer to. At least I can't find one.
Situation: If I have a bidirectional One-to-many association and I want orphanRemoval=true to take action, do I now have to remove the association from both sides of the association or is it enough to just "break" the association by deleting the reference (to the opposite) in one of the entities?
Example:
class Parent {
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
List<Child> children;
public void addChild(Child child) {
child.setParent(this);
children.add(child);
}
public void removeChild(Child child){
//both statements neccessary or just one? Does it matter which one?
children.remove(child);
if (child != null) {
child.setParent(null);
}
}
}
class Child {
String name;
#ManyToOne
#JoinColumn(name="parentID")
Parent parent;
public void setParent(Parent p) {
[...]
}
}
I came across this explanation in my book "Pro JPA 2 by Mike Keith" (2nd ed., p. 287):
When specified, the orphanRemoval element causes the child entity to be removed when the relationship between the parent and the child is broken.
This can be done either by setting to null the attribute that holds the
related entity, or additionally in the one-to-many case by removing the child entity from the collection. The provider is then responsible, at flush or commit time (whichever comes first), for removing the orphaned child entity.
In a parent-child relationship, the child is dependent upon the existence of the parent. If the parent is removed,
then by definition the child becomes an orphan and must also be removed.
And then there are those posts which do both of them:
https://stackoverflow.com/a/23926548
https://stackoverflow.com/a/3071125
https://coderanch.com/t/652044/databases/Delete-Bidirectional-Entities#3012875
My questions:
1) What's correct / which one of both are necessary now?
2) What happens if the Child class has an
#JoinColumn(name="parentID", nullable=false) and its parent
reference will get set to null as seen above? My guess: the
orphanRemoval would take place later and is based on the value in
the database, so not until the Child entity was updated. But that
update wouldn't happen, because of nullable=false, right?
I have two classes
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="PERSONTYPE")
#DiscriminatorValue(value="PERSON")
public class Parent {
.......
}
#Entity
#DiscriminatorValue(value="CHILD")
public class Child extends Parent{
.......
}
The scenario I have:
create a person -- then the PERSONTYPE = 'PERSON'
go the Person page and update it to be 'CHILD' by checking a check box 'Is Child' then after save the Person must be saved to be with type 'CHILD'.
Then how can I change the entity type from 'PERSON' to 'CHILD'?
Here are a couple possibilities:
It seems the obvious thing to do would be to setIsChild(true) on the Parent object and commit it, however I'm not sure how JPA will react to this since you are now committing a Parent and the result is a Child. Not sure this is possible. It is definitely worth a try.
Another option would be to write JPA update statement (circumventing Child and Parent objects) to update the is_child column in the database. Then when you subsequently query this record you will get a Child back not a Parent.
Lastly, you could create a child object with all values of the parent object, then delete the parent, and create the child. This will work, however aside from the extra processing required for delete / create, instead of a simple update, the id of the child may change (it will change if you are using auto generated ids). IMO, this is not a good solution, but it will work.
i assume you have unique properties for Child Object and thats why you want to use inheritance, otherwize just as #ZB Ziet commented you should just use child flag
Solution 1
i see you are using single table inheritance strategy, thus you have to modify the descriminator field manulay (by using SQL queries) and set appropirate fields on child table.
UPDATE Parent SET PERSONTYPE='CHILD' WHERE id = 1
practicaly you use native queries like this
enityManager.createNativeQuery(“UPDATE PERSON SET PERSONTYPE = ?, ”
“ VERSION = VERSION + 1 WHERE ID = ?”)
.setParameter(1, 'CHILD')
.setParameter(2,personID)
.executeUpdate();
then you can use entitymanager to get the child and set properties
entityManager.find(Child.class,1).childProp=xxxx
** Solution 2 **
IMO, The best thing to do here is instead of using single table strategy you should use joined table strategy
in joined table strategy new table entireis is created for each child having thier id as foreign key to parent entity.so changing the id will also set its parent
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Person {
...
it would have been cool if you can just create new child set the id same as parent and save. Child c = new Child(); c.setId(parent.getId()); entityManager.merge(c) . but hibernate tries to re-create a parent object resulting id confilict.
so the solution is to write native query
em.createNativeQuery("INSERT into child (id) VALUES (1)").executeUpdate(); //1 being the parent id
more reference on inheritance
Use EntityMaster
The Idea is to create an Entity just for changing the discriminator.
At least this Entity need to have the id and the discriminator.
#Entity
public class ParentMaster {
public static final PERSON="PERSON";
public static final CHILD="CHILD";
private Long id;
private String persontype;
//getter setter
}
Now load the ParentMaster by id and pm.setPersontype(ParentMaster.CHILD);.
I have an entity looks like this.
#Entity
#XmlRootElement
class Parent {
// No getters nor setters for 'children'
// Don't attack via reflection!
#OneToMany(cascade = {CascadeType.REMOVE}, mappedBy = "parent") // lazy, huh?
#XmlTransient
private Collection<Child> chilren; // MILLIONS OF THEM, say.
}
I mapped children just for Criteria Query.
#StaticMetamodel(Parent.class)
class Parent_ {
public static volatile CollectionAttribute<Parent, Child> children;
}
My question is, is it safe to map those children this way? Is it possible that chilren fetched from DB within a Parent for any case?
If you never want to access the relationship, I would not map it. You should be able to define most queries using the ManyToOne back instead of the OneToMany.
If you are using EclipseLink, you can also define query keys for relationships that are only used in queries,
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Query_Keys
A LAZY OneToMany that you never access is probably safe, but you need to be very careful you don't do anything that will cause it to instantiate. The cascade remove is probably a bad idea, as it will cause it to be instantiated on remove.
I have two entity classes user and device.
User entity:
public class User {
private Long userId;
#OneToMany( mappedBy = "userId", fetch = FetchType.LAZY)
private Collection<Device> deviceCollection;
and device entity:
public class Device implements Serializable {
#JoinColumn(name = "user_id", referencedColumnName = "user_id")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private User userId;
When I merge a previously detached device entity into the entity manager after the parent user has been deleted, both the (previously removed) user and the device are re-inserted into the database. There is no cascade annotation on user or device entity; therefore, I don't expect the user entity to be reinserted but it did;
How do I prevent the merge operation to cascade to the user entity?
Thanks in advance.
Any changes you do in detached state there is no possible way for Session Manager to know it so for it the changes are always new objects that needs to be merged (If you are calling merge)
So when you call merge it will load it from database so your object will have Prev+ new changes. So that is why mentioned behavior is happening.
What you can do is first load entity in the session apply changes and then call merge.
What you can do is something like below I have used similar relationship in one of my project with Eclipse Link
Query query = entityManager
.createNamedQuery("User.FindByUserId");
User fromDatabase = null;
try {
query.setParameter("userId", device.getUser().getUserId());
fromDatabase = (User) query.getSingleResult();
} catch (NoResultException noResultException) {
// There is no need to do anything here.
}
if (fromDatabase == null) {
User user= entityManager.merge(device.getUser());
device.setUser(user);
} else {
device.setUser(user);
}
entityManager.persist(device);
Try adding insertable=false, updatable=false to your JoinColumn, e.g.
#JoinColumn(name = "user_id", referencedColumnName = "user_id", insertable=false, updatable=false)
You should be using a version number to prevent entities from being mistakenly resurected. This will force an exception, where as the specification is a bit unclear on what should happen when merging over a relation that isn't marked cascade all or merge. The spec states that managed entities will be synchronized to the database, while the section dealing with merge implies that even entities referenced by relations without the cascade merge/all options will be managed afterward. This behavior is probably not what was intended, but shouldn't be relied on until clarified.
I had the same problem
and I found a bug about this: EntityManager.merge() cascading by default
but I really don't understand why this behaviour was never fix. It is one of reasons among others that I don't use EclipseLink (But it's not the point here)
Edit:
Chris, the comment which begin with "I'm not an expert" the argument that is put in head is not right, I think. What I understand, it's just that entity with a relation without cascade=MERGE or cascade=ALL, you can just navigate, that's all.
Otherwise why use Merge annotation ? It doesn't make sense.