I've some probleme with merge. My update method works in this way:
void update(Parent parent) {
evict(parent);
merge(parent);
}
My classes:
Parent {
Long id;
List<Children> childrens;
#OneToMany(targetEntity =ChildrenImpl.class, fetch=FetchType.LAZY)
#JoinColumn(name="PARENT")
#org.hibernate.annotations.Cascade(value = org.hibernate.annotations.CascadeType.ALL)
List<Children> getChildrens(){...}
#Id
Long getId() {...}
}
Children{
Parent parent;
#ManyToOne(targetEntity = ParentImpl.class, fetch = FetchType.LAZY)
#org.hibernate.annotations.Cascade(value = org.hibernate.annotations.CascadeType.ALL)
#JoinColumn(name = "PARENT", nullable = false)
Parent getParent(){...}
}
When i create a new Parent(transient) object and add new childrens and trying updated(evict & merge) then logs show me this after flush hibernate session:
INSERT PARENT //everythings here is ok.
INSERT CHILDREN // but without parent id(id=null)
Order is good but children doesn't have parent id in insert. Everything works fine when Parent is persisted in database, then childrens always have a good id.
Any ideas what should I do to get id from transient object(from persisted is ok).
Regards
KZ.
You cannot get an id from a transient object, by definition a transient object does not have an id.
why are you doing a merge if you create a new object? When you create a new object, you should save it. If you change the values of an existing object, you should update it. You should be merging only if an object that was persistent becomes detached, and you need to reattach it.
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 am using one to one relation by shared primary key in hibernate,
whenever I call the save method to insert parent entity without inserying child entity I get below
exception
org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.example.sms.domain.Child.parent]
Code of child class entity with mapping of parent class entity is
given below
#Entity
#Table(name = "t_child")
public class Child {
#Id
#Column(name="user_id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="user"))
private Long id;
#OneToOne(optional = false,fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Parent parent;
And in parent class entity i have mapped child class entity like below
#OneToOne(mappedBy="parent", cascade=CascadeType.ALL)
private Child child;
Is there any way to save only parent entity without inserting child entity?
The entity relationship diagram is not showing us a one-to-one relationship. But let us assume it is.
If I understood you well the child entity is dependent on the parent entity and it does not have its own identity. So you don't need to define an ID attribute for it; so the mapping for the Child entity should look like the following:
#Entity
#Table(name = "t_child")
public class Child {
#Id
#OneToOne(optional = false,fetch = FetchType.LAZY)
#JoinColumn(name="parent_id")
private Parent parent;
// ...
}
and the Parententity should look like the following:
#Entity
#Table(name = "t_parent")
public class Parent {
#Id
#GeneratedValue(generator="gen")
private Long id;
#OneToOne(mappedBy="parent")
private Child child;
// ...
}
That should be the mapping. Now, if you want to save parent without child do as follows:
Parent parent = new Parent();
// set attribute values
entityManager.persist(parent);
But if you want to save child, you should know that you first must have to save parent because the child is dependent on the parent, which means the parent must exist.
Finally after 1 day I have found the solution myself
This code Was correct. Mapstruct was creating a problem while
converting Dto to domain object
#Mappings({
#Mapping(source = "fatherName", target = "child.childDetail.fatherName"),
#Mapping(source = "motherName", target = "child.childDetail.motherName"),
#Mapping(source = "firstName", target = "child.childDetail.firstName"),
#Mapping(source = "lastName", target = "child.childDetail.lastName"),
#Mapping(source = "dateOfBirth", target = "child.childDetail.dateOfBirth"),
})
public User ParentDtoToParent(ParentDto parentDto);
I was using the Same DTO object that I was using while storing every
child detail But to store only parent entity I was not sending any
child details value in the Json Format. So mapstruct Automatically
assigned NULL value to the Child attribute(firstname,lastname etc)
When Hibernate Was saving the object it got object With the Null value
of Attributes instead of NULL Object
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");
}
The database table structure is the following:
id INT
extId VARCHAR
name VARCHAR
parent INT (references self.id)
Here is the entity
#Entity
#Table(name = "categories")
public class Category
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "parent", referencedColumnName = "id")
private Category parent;
#org.hibernate.annotations.OrderBy(clause = "name ASC")
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", cascade = CascadeType.ALL)
private Set<Category> children = new HashSet<>();
#Column(name = "extId")
private String extId;
#Column(name = "name")
private String name;
public void addChild(Category child)
{
child.setParent(this);
this.children.add(child);
}
//getters and setters ...
}
In the very beginning there is only one entity:
{
id: 0
extId: ''
name: 'Category'
parent: null
children: Set<Category>{/*empty*/}
}
This entity is fetched in the beginning of program and assigned to another class
Later this class performs addition of new Category as a child to existing root (the one that was fetched in the beginning) property
Addition of child is done like this:
Session session = HibernateSessionFactory.getInstance().getFactory().openSession();
//gather data
Category child = new Category();
//set name
//set extId
this.rootCategory.addChild(child);
Transaction tx = session.beginTransaction();
session.save(this.rootCategory);
tx.commit();
session.close();
After this instead of expected result in database:
Root(id(0), extId(''), parent(null), name('root'))
\-— Child(id(10), extId('art-0'), parent(0), name('child'))
I get the following result
Root(id(0), extId(''), parent(null), name('root'));
Root(id(10), extId(''), parent(null), name('root'))
\-— Child(id(11), extId('art-0'), parent(10), name('child'))
Notes:
New Session is created for each action and this session is acquired via Singleton SessionFactory
If I refresh() root entity before adding a child -- everything is OK, no duplicates
If I perform child addition immediately after fetching root entity -- no duplicates
What could be the reason of this behavior (assuming two different sessions)? And how can it be fixed?
After fetching from db at the beginning of your program, rootCategory becomes a detached object.
Later when you want to use it in another session, you need to reattach it to this session. Instead of session.save(), you can use session.update()
this.rootCategory.addChild(child);
Transaction tx = session.beginTransaction();
session.update(this.rootCategory);
tx.commit();
session.close();
If you are closing the session after fetching rootCategory then the rootCategory object becomes a detached object according to the hibernate lifecycle and using Session.save() on it will create a new row for it. Therefore you either need to fetch, add the child and save the rootCategory object in the same session or use refresh to tell hibernate that it is not a new object but one that is already saved.
I have model Category. It may have parent Category and list of sub Category. I wrote this questions because could not find case where entity relate to himself.
I tried to implement it like this:
#Entity
public class Category {
#Id
private String id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Category parent;
#OneToMany(mappedBy = "parent", fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
private List<Category> subcategories = Lists.newArrayList();
}
I save entities like:
Category parent = new Category();
parent.setName("parent");
Category child1 = new Category();
child1.setName("child1");
child1.setParent(parent);
parent.getSubcategories().add(child1);
categoryPersistence.save(parent);
I expected to see something like:
parent {
id
parent: null
childs {
child {
id
parent: ...lazy loading exception...
childs: null
}
}
}
But in child model on filed parent i have recursive loop.
How to prevent it?
Yeah, i also used #JsonIgnore. But i was not sure is it good practice.
But if i have a case when i need one Category and i realy need to send it to UI with parent. Is #JsonIgnore can produce this?
#damienMiheev I have also suffered from same problem, but debugging the issue 1 or half hour i figured out
That even if your parent field loaded lazily, while the automatic JSON generation getter of your field is getting called which is fetching the values, which is creating some cyclic execution.
Because it is fetching parent then fetching children and again fetching parent for every child in children collection and continuing this process until StackOverflows.
Here are the solution for your problem
You should exclude the "parent" the from json generation e.g. you can mark that field as #JsonIgnore.
you should also not include parent and children in your hashCode(), equals() and toString() method.