I have the table that represents kind of Tree node. The mapping below illustrates many-to-many mapping of node.
#Entity
public class Node {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany
#JoinTable(name = "node_dependency",
joinColumns = {#JoinColumn(name = "parent_id")},
inverseJoinColumns = {#JoinColumn(name = "child_id")})
private List<Node> childNodes = new ArrayList<>();
}
It works but I would like to have separate table mapping for the delete query simplicity.
#Entity
public class NodeRelation {
#ManyToOne
private Node parent;
#ManyToOne
private Node child;
}
If I have NodeRelation I can easily find nodes that re-used on different layers of the tree and cannot be safely deletes which is more difficult to do having instead of (One-to-many on Node + Many-to-One on FK in NodeRelation) only Many-to-Many mapping.
I tried different combinations of mapping with composite key that represented by NodeRelation but there is no luck (validation according db schema didn't passed). Please, advice me which mapping is better in this use case.
It is better to not use childNodes association in the Node.
It will be convenient to add id to the NodeRelation.
#Entity
public class Node {
#Id
#GeneratedValue
private Long id;
}
#Entity
public class NodeRelation {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Node parent;
#ManyToOne
private Node child;
}
Also you can add an unique constraint (parent, child) to NodeRelation (to have the same behavior as #ManyToMany join table has).
It will need to do queries on NodeRelation table only.
Related
I have a parent table, and I am trying to create a #OneToMany relationship between the parent and child tables (e.g. one parent table can be associated with several child tables).
Parent.java
#Entity
#Table(name = "parent")
public class Threat {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long parentId;
#Column(name = "name")
private String parentName;
}
Child.java
#Entity
#Table(name = "child")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long childId;
#Column(name = "parent_id")
private long parentId;
#Column(name = "name")
private String childName;
#Column(name = "age")
private Integer childAge;
}
I am having trouble figuring out how to do this given the fact I need to join the primary key of the parent table (parent.id) to a column in the child table that is not the primary key (child.parent_id). Ideally, when I return a Parent object, I would like both the parent id and the list of children displayed, so the parent id is listed in both the parent and children. That way, if the parent has no children, I still have the parent id in my object.
I have been doing some research, and I'm wondering if I should be using
#PrimaryKeyJoinColumn
and/or
#Inheritance(strategy = InheritanceType.JOINED)
I haven't been able to figure out how to implement them though. Any help would be greatly appreciated.
Checkout #JoinColumn.
From Java doc :
Specifies a column for joining an entity association or element collection. If the JoinColumn annotation itself is defaulted, a single join column is assumed and the default values apply.
Example:
#ManyToOne
#JoinColumn(name="ADDR_ID")
public Address getAddress() { return address; }
Example: unidirectional one-to-many association using a foreign key
mapping
// In Customer class
#OneToMany
#JoinColumn(name="CUST_ID") // join column is in table for Order
public Set<Order> getOrders() {return orders;}
With this your entity should look like the following :
#Entity
#Table(name = "parent")
public class Threat {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long parentId;
#Column(name = "name")
private String parentName;
#OneToMany
#JoinColumn(name="parent_id")
private List<Child> children = new ArrayList<>();
}
Now whenever you fetch Parent , you can access the associated child entities.
Additionally you can specify #OneToMany(fetch = FetchType.EAGER) if you always need child entities fetched whenever you query for parent.
Say I have a unidirectional #ManyToOne relationship like the following:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
}
#Entity
public class Child implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn
private Parent parent;
}
If I have a parent P and children C1...Cn referencing back to P, is there a clean and pretty way in JPA to automatically remove the children C1...Cn when P is removed (i.e. entityManager.remove(P))?
What I'm looking for is a functionality similar to ON DELETE CASCADE in SQL.
If you are using hibernate as your JPA provider you can use the annotation #OnDelete. This annotation will add to the relation the trigger ON DELETE CASCADE, which delegates the deletion of the children to the database.
Example:
public class Parent {
#Id
private long id;
}
public class Child {
#Id
private long id;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
With this solution a unidirectional relationship from the child to the parent is enough to automatically remove all children. This solution does not need any listeners etc. Also a JPQL query like DELETE FROM Parent WHERE id = 1 will remove the children.
Relationships in JPA are always unidirectional, unless you associate the parent with the child in both directions. Cascading REMOVE operations from the parent to the child will require a relation from the parent to the child (not just the opposite).
You'll therefore need to do this:
Either, change the unidirectional #ManyToOne relationship to a bi-directional #ManyToOne, or a unidirectional #OneToMany. You can then cascade REMOVE operations so that EntityManager.remove will remove the parent and the children. You can also specify orphanRemoval as true, to delete any orphaned children when the child entity in the parent collection is set to null, i.e. remove the child when it is not present in any parent's collection.
Or, specify the foreign key constraint in the child table as ON DELETE CASCADE. You'll need to invoke EntityManager.clear() after calling EntityManager.remove(parent) as the persistence context needs to be refreshed - the child entities are not supposed to exist in the persistence context after they've been deleted in the database.
Create a bi-directional relationship, like this:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private Set<Child> children;
}
I have seen in unidirectional #ManytoOne, delete don't work as expected.
When parent is deleted, ideally child should also be deleted, but only parent is deleted and child is NOT deleted and is left as orphan
Technology used are Spring Boot/Spring Data JPA/Hibernate
Sprint Boot : 2.1.2.RELEASE
Spring Data JPA/Hibernate is used to delete row .eg
parentRepository.delete(parent)
ParentRepository extends standard CRUD repository as shown below
ParentRepository extends CrudRepository<T, ID>
Following are my entity class
#Entity(name = “child”)
public class Child {
#Id
#GeneratedValue
private long id;
#ManyToOne( fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = “parent_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
#Entity(name = “parent”)
public class Parent {
#Id
#GeneratedValue
private long id;
#Column(nullable = false, length = 50)
private String firstName;
}
Use this way to delete only one side
#ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
// #JoinColumn(name = "qid")
#JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = #ForeignKey(name = "qid"), nullable = false)
// #JsonIgnore
#JsonBackReference
private QueueGroup queueGroup;
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
Given annotation worked for me. Can have a try
For Example :-
public class Parent{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="cct_id")
private Integer cct_id;
#OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private List<Child> childs;
}
public class Child{
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="cct_id")
private Parent parent;
}
You don't need to use bi-directional association instead of your code, you have just to add CascaType.Remove as a property to ManyToOne annotation, then use #OnDelete(action = OnDeleteAction.CASCADE), it's works fine for me.
I'm looking for how to solve a recursion and bidiretional onetoone relationship in my Branch object:
#Entity
#Table("BRANCH")
public class Branch {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToOne
#JoinColumn(name = "id")
private Branch parent;
#OneToOne(mappedBy = "parent")
#JoinColumn(name = "parent_id")
private Branch child;
//Getters and Setters
}
Is it an accepted pattern?
Right way of bidirectional #OneToOne mapping with self reference:
#Entity
#Table("BRANCH")
public class Branch {
#OneToOne
#JoinColumn(name = "parent_id")
private Branch parent;
#OneToOne(mappedBy = "parent")
private Branch child;
this is ancestor for object in child field
this is descendant for object in parent field
Hierarchy is: parent > this > child
You do not require two foreign keys, single
foreign key in the owning side of the relationship is sufficient. In
JPA the inverse OneToOne must use the mappedBy attribute.
https://en.wikibooks.org/wiki/Java_Persistence/OneToOne#Inverse_Relationships,_Target_Foreign_Keys_and_Mapped_By
I have a unidirectional, one-to-many, parent/child relation.
In my test case, I have 1 parent with 2 children which are inserted via cascading insert.
Looking at the queries that are ran, I have 1 insert for the parent, 1 insert and two update queries for each of the children. The updates for the foreign key - they are setting the parent_id column in the child table, but I can see that the parent_id has already been set correctly by the insert.
Here is an example
#Entity
#Table(name = "PARENT")
public class Parent
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long parentId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "parent_id", nullable=false)
private List<Child> children;
}
#Entity
#Table(name = "CHILD")
public class Child
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "PARENT_ID")
private Long parentId;
//some other field
}
//The test looks like this
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();
//set all fields
parent.addChild(child1);
parent.addChild(child2);
em.merge(parent);
Is it possible to not have the update queries?
Is it possible to insert all children in a single query?
You might try persist instead of merge
JPA EntityManager: Why use persist() over merge()?
No. It is not possible. because after insert in parent table, a child table can be inserted.
I'm trying to create simple select query with HQL.
There is an Entity that will be used in query. It Looks like the following:
#Entity
#Table(name = "my_table")
public class MyTable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "test_id")
private Long testId;
#Column(name = "parent_id")
private Long parentId;
#OneToMany(mappedBy = "parentId",
fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private Set<MyTable> children = new HashSet<MyTable>();
//getters and setters
}
Hierarchy is simple. There are Parents (which have null parent_id value) and their children. So two levels only.
I would like to create query that selects all parents and their children, but there is a condition for children: it should equal to specific test_id. E.g. it is required to have children only with test_id = 1. Table consists of Parent1 with Child1 (test_id = 2) and Parent2 with Child2 (test_id = 1). The query result should be Parent1 without children and Parent2 with Child2.
Query:
from MyTable as myTable left fetch join myTable.children as child
where child.testId = 1
As a result - I'm getting only those parents which have children with "1" test_id. But I need all parents to see even if there no needed children.
What is wrong here: mapping or query? And how should it actually be?
Thanks in advance.
Your mapping is wrong. You have a unidirectional OneToMany association. SO you obviously must tell Hibernate how this association is mapped. And what you tell is:
#OneToMany(mappedBy = "parentId", ...)
This means: "Go look at the other side of this bidirectional association to find how this association is mapped. I'm only the inverse side".
But there is no other side. Your association is unidirectional.
The mapping should be:
#Entity
#Table(name = "my_table")
public class MyTable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "test_id")
private Long testId;
#OneToMany(fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
#JoinColumn(name = "parentId")
private Set<MyTable> children = new HashSet<MyTable>();
//getters and setters
}
Note that the parentId field has been removed, since it's already used to map the children association.
Another problem is your expectations. A query returns columns, or it returns entities. If it returns a parent entity and you ask this entity for its children, all the children will be returned. AN entity represents what is in the database. It doesn't represent the result of a specific query.
What you could do, if you only want to have some of the children, is to search for children, and get their parent. To do that, you would need to make the association bidirectional:
#Entity
#Table(name = "my_table")
public class MyTable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "test_id")
private Long testId;
#ManyToOne
#JoinColumn(name="parentId")
private MyTable parent;
#OneToMany(mappedBy="parent",
fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
#JoinColumn(name = "parentId")
private Set<MyTable> children = new HashSet<MyTable>();
//getters and setters
}
And the query would be:
select child from MyTable child
left join fetch child.parent
where child.testId = 1
This would return all the children with testId = 1, along with their parent. You would need a second query to get all the other parents. Something like
select parent from MyTable parent
where parent.id not in (
select parent2.id from MyTable child
left join child.parent parent2
where child.testId = 1)