Here is entities:
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parent" ,cascade = CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
private Collection<Child> children;
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
private Parent parent;
}
For managing this entities I use spring data (rest). I need to save entities like that:
Parent parent = new Parent();
Child child = new Child()
parent.setChildren(Arrays.asList(child))
springDataRepository.save(parent);
And only Parent object is persisted.
Also as I spot parent and child are created without id. So should I persist only parent, then set it to child and persist child? Can these operation be done with one parent saving action?
See this reply: https://stackoverflow.com/a/7428143
Instead of:
parent.setChildren(Arrays.asList(child))
you should use:
parent.setChildren(new ArrayList<>(Arrays.asList(child)))
Related
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 need to delete my child entity if one of the parent are being removed.
Right now child entity will be removed if both parents are removed and I need to change the behaviour.
For example if you have:
class Parent1 {
#Id
Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
Set<Child> children = new HashSet();
}
class Parent2 {
#Id
Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Set<Child> children = new HashSet();
}
class Child {
#Id
Long id;
#ManyToOne
Parent1 p1;
#ManyToOne(fetch = FetchType.EAGER)
Parent2 p2;
}
I tried to use some combinations with orphanRemoval, but it didn't work. Do you have any other suggestions?
I'd put this kind of logic simply in a method that removes a parent. It would then also remove the other parent. This method could live on the Child class or a separate service.
Note, that currently your mapping is missing mappedBy values for the bidirectional relationships.
I have the following "parent class" :
#Entity
#Table(name = "parent_table")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long parentId;
private String firstName;
private String lastName;
#OneToMany(mappedBy = "Parent", cascade = CascadeType.ALL)
List<Child> children;
}
I also have the following child class :
#Entity
#Table(name = "children")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childId;
#ManyToOne
#JoinColumn(name="parent_id")
private Parent parent;
private String name;
}
I am sending up the following request in postman :
{
"firstName": "Test",
"lastName": "Parent",
"children":[{
"name":"jack"
},
{
"name":"jill"
}
]
}
When I ask the parent repository to save the parent, it does, but nothing happens for the child... it doesn't save to the database at all...
For reference, this is my line that saves the parent
parentRepository.save(parent)
(the parent in this case, has the two children inside of it - but they don't get saved to the children table).
I ran your example and it seems to be working correctly, only thing i change was mappedBy property of #OneToMany annotation. It must be lower case.
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
List<Child> children = new ArrayList<>();
Parent
#Entity
#Table(name = "parent_table")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "parent_id")
private Long parentId;
private String firstName;
private String lastName;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
List<Child> children = new ArrayList<>();
}
Child
#Entity
#Table(name = "children")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childId;
#ManyToOne
#JoinColumn(name="parent_id")
private Parent parent;
private String name;
}
Test
Above code makes next unit case to succeed:
#Test
public void parentRepositoryMustPersistParentAndChildren() {
Parent parent = new Parent("Anakin", "Skywalker");
parent.getChildren().add(new Child("Luke"));
parent.getChildren().add(new Child("Leia"));
Parent saved = parentRepository.save(parent);
Assert.assertNull("Parent does not have and id assigned after persist it", saved.getParentId());
saved.getChildren().forEach((child) ->{
Assert.assertNull("Parent does not have and id assigned after persist it", child.getChildId());
});
}
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.
Im facing a little problem here.
I have two entities: Parent and Child, Parent has a List annotated #OneToMany.
The problem is when I try to insert a new Parent, it crashes when persisting the children, because the Parent Id was not generated yet.
Is that a fix for it?
#Entity
#Table(name = "PRODUTO")
public class Parent extends BaseEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID_PRODUTO")
private Integer produtoId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "produtoId", orphanRemoval = true)
// #JoinTable(name = "PRODUTO_TAMANHO", joinColumns = #JoinColumn(name = "ID_PRODUTO"))
#OrderBy("preco ASC")
private List<Child> children;
}
#Entity
#IdClass(Child.PrimaryKey.class)
#Table(name = "PRODUTO_TAMANHO")
public class Child extends BaseEntity
{
public static class PrimaryKey extends BaseEntity
{
private static final long serialVersionUID = -2697749220510151526L;
private Integer parentId;
private String tamanho;
//rest of implementation
}
#Id
#Column(name = "ID_PRODUTO")
private Integer parentId;
#Id
#Column(name = "TAMANHO")
private String tamanho;
#ManyToOne
#JoinColumn(name = "ID_PRODUTO", insertable = false, updatable = false)
private Parent parent;
}
I think if I persist firstly the parent, than persist the children would be a bad approach.
Is that a way to persist the children, when persisting Parent?
Thanks!
Guys, the exception that occurs when persisting Parent is:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'ID_PRODUTO' cannot be null
I found a guy facing the same problem: #OneToMany and composite primary keys? (maybe it's better explained)
Here is my insertion code
Parent parent = new Parent();
Child child1 = new Child();
child1.setTamanho("Tamanho 1");
child1.setParent(parent);
Child child2 = new Child();
child2.setTamanho("Tamanho 1");
child2.setParent(parent);
List<Child> children = parent.getChildren();
children.add(child1);
children.add(child2);
save(parent);
//all of this instances, is coming from a view.jsp binded by spring, I can confirm it is exactly like this, with parentId as null
//when updating, it goes perfectly
There are few problems with your entity class.
mappedBy attribute in Parent entity should be set to parent: mappedBy="parent".
In child entity, below field is not required.
#Id
#Column(name = "ID_PRODUTO", nullable = true)
private Integer parentId;
Updated entity is like this.
#Entity
#Table(name = "PRODUTO")
public class Parent extends BaseEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID_PRODUTO")
private Integer produtoId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval = true)
// #JoinTable(name = "PRODUTO_TAMANHO", joinColumns = #JoinColumn(name = "ID_PRODUTO"))
#OrderBy("preco ASC")
private List<Child> children;
}
#Entity
#IdClass(Child.PrimaryKey.class)
#Table(name = "PRODUTO_TAMANHO")
public class Child extends BaseEntity
{
public static class PrimaryKey extends BaseEntity
{
private static final long serialVersionUID = -2697749220510151526L;
private Integer parentId;
private String tamanho;
//rest of implementation
}
/* #Id
#Column(name = "ID_PRODUTO", nullable = true)
private Integer parentId; */ // Not required.
#Id
#Column(name = "TAMANHO")
private String tamanho;
#ManyToOne
#JoinColumn(name = "ID_PRODUTO", insertable = false, updatable = false)
private Parent parent;
}
Also I do not understand child inner class for primary key. Use proper primary as you have used parent.
And while inserting set both parent to child and child to parent. See my blog for more details.Here