I have a legacy database, which I am using EJB3 to model. The database is in quite a poor shape, and we have certain unusual restrictions on how we insert into the DB. Now I want to model the database in a hierarchy that fits in with the DB strucuture, but I want to be able to manually insert each entity individually without the persistence manager trying to persist the entities children.
I am trying something like the following (boilerplate left out):
#Entity
#Table(name = "PARENT_TABLE")
public class Parent {
#Id
#Column(name = "ID")
int id;
#OneToMany
List<Child> children;
}
#Entity
#Table(name = "CHILD_TABLE")
public class Child {
#Id
#Column(name = "ID")
int id;
}
Now this throws an exception:
java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
Now I know the entity isn't marked PERSIST - I don't want the EntityManager to persist it! I want to be able to persist the parent first, and then the child - but not together. There are good reasons for wanting to do it this way, but it doesn't seem to want to play.
Heh welcome to the hair-pulling that is JPA configuration.
In your case you have two choices:
Manually persist the new object; or
Automatically persist it.
To automatically persist it you need to annotate the relationship. This is a common one-to-many idiom:
#Entity
#Table(name = "PARENT_TABLE")
public class Parent {
#Id private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private Collection<Child> children;
public void addChild(Child child) {
if (children == null) {
children = new ArrayList<Child>();
}
child.setParent(parent);
children.add(child);
}
}
#Entity
#Table(name = "CHILD_TABLE")
public class Child {
#Id private Long id;
#ManyToOne
private Parent parent;
public void setParent(Parnet parent) {
this.parent = parent;
}
}
Parent parent = // build or load parent
Child child = // build child
parent.addChild(child);
Because of the cascade persist this will work.
Note: You have to manage the relationship at a Java level yourself, hence manually setting the parent. This is important.
Without it you need to manually persist the object. You'll need an EntityManager to do that, in which case it is as simple as:
entityManager.persist(child);
At which point it will work correctly (assuming everything else does).
For purely child entities I would favour the annotation approach. It's just easier.
There is one gotcha I'll mention with JPA:
Parent parent = new Parent();
entityManager.persist(parent);
Child child = new Child();
parent.addChild(child);
Now I'm a little rusty on this but I believe that you may run into problems if you do this because the parent was persisted before the child was added. Be careful and check this case no matter what you do.
Related
Let say I have an app to handle a collection of books.
My app allow to add a new book to the library. When creating the book, user can select the Author in the list, and if the author doesn't exist yet, he's able to add him to the list, providing his name to a form field.
When the form is filled, data are sent to a WS, something like
{
"name" : "The Book name"
"author" : {
"name" : "author's name"
}
}
Then I map json into my entity which would be
Book :
#Entity
#Table(name = "book")
public class Book{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Author author;
}
Author
#Entity
#Table(name = "author")
public class Author{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "author", cascade = { CascadeType.ALL })
private List<Book> books;
}
This will not work as if user tries to add a new author, when I'll try to .save() I'll get an error :
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance
Is there a way to handle the case with Spring-Data-Jpa, or do I have to check manually that I got an author id in the json, and if not - meaning that this is a new author - mannually run the author creation and then save the new book?
Thx!
As you're guessing, and as the Javadoc says, cascade operations that must be cascaded to the target of the association". However, be sure you understand that the mappedBy defines the owning entity of the relationship. The owning entity is the entity that actually does the persisting operations, unless overridden by a cascade setting. In this case Child is the owning entity.
#Entity
public class Parent {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy="parent")
private Set<Child> children;
The cascade setting on the Parent works when you create a Set of children and set it into the Parent and then save the Parent. Then the save operation will cascade from the Parent to the children. This is a more typical and the expected use case of a cascade setting. However, it does cause database operations to happen auto-magically and this is not always a good thing.
A Cascade setting on the child will happen when the child is persisted, so you could put a cascade annotation there, but read on ...
#Entity
public class Child {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade=CascadeType.ALL)
private Parent parent;
You will persist both the parent and the child by persisting the child.
tx.begin();
Parent p = new Parent();
Child c = new Child();
c.setParent(p);
em.persist(c);
tx.commit();
and when you delete the child it will delete both the parent and the child.
tx.begin();
Child cFound = em.find(Child.class, 1L);
em.remove(cFound);
tx.commit();
em.clear();
this is where you have problems. What happens if you have more than one child?
em.clear();
tx.begin();
p = new Parent();
Child c1 = new Child();
Child c2 = new Child();
c1.setParent(p);
c2.setParent(p);
em.persist(c1);
em.persist(c2);
tx.commit();
All well and nice until you delete one of the children
em.clear();
tx.begin();
cFound = em.find(Child.class, 2L);
em.remove(cFound);
tx.commit();
then you will get an integrity constraint violation when the cascade propagates to the Parent but there is still a second Child in the database. Sure you could cure it by deleting all the children in a single commit but that's getting kind of messy isn't it?
Conceptually people tend to think that propagation goes from Parent to Child and so it is very counterintuitive to have it otherwise. Further, what about a situation where you don't want to delete the author just because the store sold all his or her books? In this case you might be mixing cascade, sometimes from child to parent and in other cases from parent to child.
Generally I think it is better to be very precise in your database code. It's much easier to read, understand, and maintain code that specifically saves the parent first then the child or children than to have an annotation somewhere else that I may or may not be aware of that is doing additional database operations implicitly.
I have the following entities:
#Entity
public static class Parent {
#Id
#GeneratedValue
private Long id;
String st;
#OneToMany(mappedBy = "parent")
Set<Child> children = new HashSet<>();
// get,set
}
#Entity
public static class Child {
#Id
#GeneratedValue
private Long id;
String st;
#ManyToOne()
private Parent parent;
//get,set
}
Note, that there is no Cascade on #OneToMany side.
And I want the following:
I have one Parent with one Child in Detached state.
Now I want to remove child by some condition, so I'm accesing all children, find necessary and remove it directly via em.remove(child). + I remove it from Parent's collection.
After that I want to change some property of Parent and save it also.
And I'm getting EntityNotFound exception.
I performed some debug, and found that children collection is PersistentSet which remembered it's state in storedSnapshot. So, when I'm merging Parent to context - Hibernate do something with that stored snapshot and tries to load child it from DB. Of course, there is no such entity and exception is thrown.
So, there are couple of things I could do:
Map collection with #NotFound(action = NotFoundAction.IGNORE)
During removing from children collection - cast to PersistentSet and clear it also.
But it seems like a hack.
So,
1. What I'm doing wrong? It seems, that it's correct to remove child entity directly
2. Is there more elegant way to handle this?
Reproducible example:
#Autowired
PrentCrud parentDao;
#Autowired
ChiildCrud childDao;
#PostConstruct
public void doSomething() {
LogManager.getLogger("org.hibernate.SQL").setLevel(Level.DEBUG);
Parent p = new Parent();
p.setSt("1");
Child e = new Child();
e.setParent(p);
e.setSt("c");
p.getChildren().add(e);
Parent save = parentDao.save(p);
e.setParent(save);
childDao.save(e);
Parent next = parentDao.findAll().iterator().next();
next.setSt("2");
next.getChildren().size();
childDao.deleteAll();
next.getChildren().clear();
if (next.getChildren() instanceof PersistentSet) { // this is hack, not working without
((Map)((PersistentSet) next.getChildren()).getStoredSnapshot()).clear();
}
parentDao.save(next); // exception is thrwn here without hack
System.out.println("Success");
}
have you tried changing fetch type to eager? defaults for relations
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
maybe it gets cached because of fetch method
You can use next.setChildren(new HashSet<>()); instead of next.getChildren().clear(); to get rid of the getStoredSnapshot()).clear()
But it would be more elegant to use cascade and orphanRemoval.
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
Set<Child> children = new HashSet<>();
public void doSomething() {
...
next.setSt("2");
next.setChildren(new HashSet<>());
parentDao.save(next);
System.out.println("Success");
}
Lets say I have bidirectional one-to-many association between Parent-Child, mapped as follows:
Parent.java:
#Entity
public class Parent {
#Id
private Integer id;
#OneToMany(mappedBy = "parent")
private List<Child> childs = new ArrayList<>();
...
and Child.java:
#Entity
public class Child {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
...
When I run this code
Parent parent = new Parent(1);
Child child = new Child(1);
Child child2 = new Child(2);
child.setParent(parent);
child2.setParent(parent);
parent.getChilds().add(child);
parent.getChilds().add(child2);
parentRepository.save(parent);
I get exception
Unable to find Child with id 1
Saving a child first doesn't help either, only exception is different
well i am sorry for posting a not sure answer but i cannot post a comment cause of reputation.
i think you have a cross reference problem, because simply by referencing the parent from the child you can get the childs that parent has with a simple query. instead you cross reference the child association resulting to many object problems. if you want i can post you a class diagram for better explanation. hope it helps
Try
#OneToMany(mappedBy = "parent", cascade={CascadeType.PERSIST})
private List<Child> childs = new ArrayList<>();
(see also JPA #ManyToOne with CascadeType.ALL for example)
I am trying to configure this #OneToMany and #ManyToOne relationship but it's simply not working, not sure why. I have done this before on other projects but somehow it's not working with my current configuration, here's the code:
public class Parent {
#OneToMany(mappedBy = "ex", fetch= FetchType.LAZY, cascade=CascadeType.ALL)
private List<Child> myChilds;
public List<Child> getMyChilds() {
return myChilds;
}
}
public class Child {
#Id
#ManyToOne(fetch=FetchType.LAZY)
private Parent ex;
#Id
private String a;
#Id
private String b;
public Parent getParent(){
return ex;
}
}
At first, I thought it could be the triple #Id annotation that was causing the malfunction, but after removing the annotations it still doesn't work. So, if anyone have any idea, I am using EclipseLink 2.0.
I just try to execute the code with some records and it returns s==0 always:
Parent p = new Parent();
Integer s = p.getMyChilds().size();
Why?
The problem most probably is in your saving because you must not be setting the parent object reference in the child you want to save, and not with your retrieval or entity mappings per se.
That could be confirmed from the database row which must be having null in the foreign key column of your child's table. e.g. to save it properly
Parent p = new Parent();
Child child = new Child();
p.setChild(child);
child.setParent(p);
save(p);
PS. It is good practice to use #JoinColumn(name = "fk_parent_id", nullable = false) with #ManyToOne annotation. This would have stopped the error while setting the value which resulted in their miss while you are trying to retrieve.
All entities need to have an #Id field and a empty constructor.
If you use custom sql scripts for initialize your database you need to add the annotation #JoinColumn on each fields who match a foreign key :
example :
class Parent {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
public Parent() {}
/* Getters & Setters */
}
class Child {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
/* name="<tablename>_<column>" */
#JoinColumn(name="Parent_id", referencedColumnName="id")
private int foreignParentKey;
public Child () {}
}
fetch= FetchType.LAZY
Your collection is not loaded and the transaction has ended.
i have a Question about referencing ParentEntities from Child Entites ir
If i have something like this:
Parent.java:
#Entity(name ="Parent")
public class Parent {
#Id
#Generate.....
#Column
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent")
private Set<Child> children;
simple ... getter and setter ...
}
And the Child.java:
#Entity(name ="Child")
public class Child{
#Id
#Generate....
#Column
private int id;
#ManyToOne
private Parent parent;
... simple getter an setter
}
Following Tables are going to be created:
Parent:
int id
Child:
int id
int parent_id (foreign key: parent.id)
Ok, so far, everthings fine. But when it comes to using this Reference from Java, i would think, you can do something like this.
#Transactional
public void test() {
Parent parent = new Parent();
Child child = new Child();
Set<Child> children = new HashSet<Child>();
children.add(child);
parent.setChildren(children);
entityManager.persist(parent);
}
which leads to this in Database:
Parent:
id
100
Child
id paren_id
101 100
But thats not the case, you have to explicity set the Parent to the Child (which, i would think, the framework could probably do by itself).
So whats really in the database is this:
Parent:
id
100
Child
id paren_id
101 (null)
cause i haven't set the Parent to the Child. So my Question:
Do I really have to do sth. like this?
Parent.java:
...
setChildren(Set<Child> children) {
for (Child child : children) {
child.setParent.(this);
}
this.children = children;
}
...
Edit:
According to the fast Replies i was able to solve this Problem by using the #JoinColumn on the Reference-Owning Entity. If we take the Example from above, i did sth. like this:
Parent.java:
#Entity(name ="Parent")
public class Parent {
#Id
#Generate.....
#Column
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name= "paren_id")
private Set<Child> children;
simple ... getter and setter ...
}
And the Child.java:
#Entity(name ="Child")
public class Child{
#Id
#Generate....
#Column
private int id;
... simple getter an setter
}
Now if we do this:
#Transactional
public void test() {
Parent parent = new Parent();
Child child = new Child();
Set<Child> children = new HashSet<Child>();
children.add(child);
parent.setChildren(children);
entityManager.persist(parent);
}
The Reference is correctly set by the Parent:
Parent:
id
100
Child
id paren_id
101 100
Do I really have to do sth. like this?
That is one strategy, yes.
On bi-directional relationships there is an "owning" and a "non-owning" side of the relationship. Because the owning side in your case is on Child, you need to set the relationship there for it to be persisted. The owning side is usually determined by where you specify #JoinColumn, but it doesn't look like you're using that annotation, so it's likely being inferred from the fact that you used mappedBy in the Parent annotation.
You can read a lot more about this here.
It still seems to be the case. In parent Entity you can have something like
#PrePersist
private void prePersist() {
children.forEach( c -> c.setParent(this));
}
in order to avoid repeating code for setting child/parent relationship elsewhere in code.
Yes, that is the case. JPA does not keep care about consistency of your entity graph. Especially you have to set it to the owner side of bidirectional relationship (in your case to the parent attribute of Child).
In JPA 2.0 specification this is said with following words:
Note that it is the application that bears responsibility for
maintaining the consistency of run- time relationships—for example,
for insuring that the “one” and the “many” sides of a bidi- rectional
relationship are consistent with one another when the application
updates the relationship at runtime.
We ran into a problem while persisting a simple object graph like the one shown above. Running in H2 everything would work, but when we ran against MySQL the "paren_id" in the child table (defined in the #JoinColumn annotation) wasn't getting populated with the generated id of the parent - even though it was set as a non-null column with a foreign key constraint in the DB.
We'd get an exception like this:
org.hibernate.exception.GenericJDBCException: Field 'paren_id' doesn't have a default value
For anyone else who might run into this, what we eventually found was that we had to another attribute to the #JoinColumn to get it to work:
#JoinColumn(name="paren_id", nullable=false)
If I am getting you correctly, according to EntityManager, if you want it to manage the transaction's insert order your have to "tell him" that it should persist the children too. And you are not doing that, so "he" doesn't know what to persist, but your parent's child list is not empty so "he" takes it has correct but the stored value is null.
So you should consider do something like:
... begin, etc
em.persist(child)
em.persist(parent)
do what you want with the parent object here then commit and this should work for similar cases too.