Hibernate predicate to get children based on parent - java

I have following structure:
#Entity
class Parent {
#Id
int id;
#OneToMany(fetch = FetchType.LAZY)
List<Child> children;
}
#Entity
public class Child {
#Id
int id;
#ManyToOne
Parent parent;
}
I want to get children based on a specific parent id. As I want to paginate the result I want to build predicate for children and not just fetch parent and get all the children this way.
So, I imagining predicate like this for the Child query:
builder.equal(root.get("parent.id"), parentId);
But this obviously will not work. So, what is the right way to do it?
Thanks

parent.id is not a property name, but a property path. If you read the java doc, you will see that Path#get requires a property name though. So to fix this, use root.get("parent").get("id")

Related

Hibernate dont allow to delete child element

Let's say I have 2 entities like that
class Parent {
String name;
Child children;
}
class Child {
String name;
Parent parent;
}
The thing is that I don't want to allow deleting child from database if it's associated with any Parent -> child in parent can't be null. Is there any way to do it? I could just check it with some forloop everytime i try to delete child from db (like query all parents and check their childId), but it doesn't seem to be very efficient.
Probably your sample should be:
public class Parent {
#Id
#GeneratedValue
private long id;
#OneToMany(optional=false)
private Set<Child> children;
// getter/setter
...
}
public class Child {
#Id
#GeneratedValue
private long id;
private String name;
// getter/setter
...
}
So take a look in this annotation: #OneToMany(optional=false), it is for enforcing NOT NULL constraint.
Here you can find more information: http://docs.jboss.org/hibernate/core/4.2/manual/en-US/html_single/#d5e5674
Your question is not well defined, but i will asume you are using tags like #Entity since you put the Hibernate and jpa tags.
In this case you should use a #OneToMany relationship (Parent -> child) and #ManyToOne in (Child -> Parent). If you do so one of them will store the id(or whatever you use as id column). When you do the addParent method and removeParent (from Child) just don't delete the other and that's it.

Why does not FetchType.Lazy work?

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.

Hibernate annotations work better the "wrong" way around?

I've been looking around the net for a decent answer to this but all I've gotten is confused. I'm struggling with how #ManyToOne annotations in hibernate are supposed to work - because #OneToMany with #JoinColumn seems far superior because hibernate inserts the foreign keys properly instead of me having to assign the child object in java before saving.
Basically as the most simple example I have something like this:
Parent Table: id int,
Child Table : id int, parentFk int
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany
#JoinColumn(name = "parentFk")
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#Column
private Integer parentFk;
//setters and getters
}
Now if I send some json that maps to a Parent class and ask hibernate to save it, it will save everything in the table including the new id of the parent in the parentFk field. However I've been lead to believe that this is actually the wrong way of doing things and that I should be doing #OneToMany(mappedBy = "id") in the Parent class instead - and then also having #ManyToOne with #JoinColumn and a Parent object in the Child class.
The problem is doing it this way I have to manually set the parent object in the child via java code before hibernate will save the id of the parent in the parentFk field correctly...it just seems like a very long winded of doing something that is already working perfectly for me (albeit I cannot access the parent object from the child).
Moreover I've tried to remove the parentFk field in the child object and use #ManyToOne with a parent object reference, but hibernate doesn't seem to like it. Am I doing this all wrong?
Your mapping should be something like this.
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy="parent", cascade={CascadeType.ALL})
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name="parentFk")
private Parent parent;
//setters and getters
}
You should also have an add(Child child) method on your parent (and remove) to manage the relation ship. And assuming Parent and Child are in the same package I would make the setParent on the Child package protected, i.e no access modifier (setParent(Parent parent) { this.parent=parent}).
public void add(Child child) {
child.setParent(this);
this.children.add(child);
}
When doing it like this hibernate will be able to execute the right queries.
Fixed by using #JsonManagedReference on the #OneToMany mappings and #JsonBackReference on the #ManyToOne

How to save bidirectional association without cascading using Spring data JPA?

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)

JPA #OneToMany -> Parent - Child Reference (Foreign Key)

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.

Categories