Can orphanRemoval be used for depth > 1? - spring

Should all child1 and child2 (depth 2) be deleted, when a parent gets deleted?
Database is Informix, constraints are created in the child tables. Deletion of parent is performed with JpaRepository.deleteById(parent.getId()), both do nothing and no error message occurs (show_sql just lists selects). Spring version is 5.3.19, spring-data-jpa 2.6.4.
Current example code:
#Entity
#Table(name = "parent_table")
public class Parent
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "parent", fetch = FetchType.EAGER)
private Set<Child1> children = new HashSet<>();
}
#Entity
#Table(name = "child1_table")
public class Child1
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "parentid", referencedColumnName = "id", nullable = false)
private Parent parent;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "child1", fetch = FetchType.EAGER)
private Set<Child2> children = new HashSet<>();
}
#Entity
#Table(name = "child2_table")
public class Child2
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "child1id", referencedColumnName = "id", nullable = false)
private Child1 child1;
}
Update
added
#PreRemove
private void deleteChildren()
{
children.clear();
}
to Parent and Child1. Now children get deleted, but not the Parent.

In fact, Parent also had a parent and I had to remove this from it's Set too.
So the solution is:
Clear children Sets
#PreRemove
private void preRemove()
{
children.clear();
}
Remove the root entity from its parent in case it has a parent
#PreRemove
private void preRemove()
{
children.clear();
parentsParent.getParents().remove(this);
}

Related

Hibernate OneToOne relationship needs child value to be passed from parent domain object

I have created the below OneToOne relationship with Hibernate and Kotlin. However, when I am Initializing Parent() it requires me to set child value as Parent(child=null) which is not desired. Only initializing child should require parent as Child(parent=Parent(...) and if I add both parent to child and child to parent, it creates an infinite loop. What it the right way to avoid that?
#Entity
class Parent(
#Id
#Column(nullable = false, updatable = false, columnDefinition = "uuid")
val id: UUID = UUID.randomUUID(),
#OneToOne(cascade = [CascadeType.ALL], mappedBy = "parent")
#JsonIgnore
#JoinColumn(name = "child_id", referencedColumnName = "id")
val child: Child?
)
#Entity
class Subscriber(
#Id
#Column(nullable = false, updatable = false, columnDefinition = "uuid")
val id: UUID = UUID.randomUUID(),
#OneToOne(cascade = [CascadeType.ALL], optional = false)
#JoinColumn(name = "id", columnDefinition = "uuid")
#MapsId
val parent: Parent
)
As parent and child are mapped one to one and you want to use #MapsId to not create another extra PK in child table. Now Child object will use parent_id has its own PK.
For Parent
#Entity
public class Parent {
#Id
#Column(nullable = false, updatable = false, columnDefinition = "uuid")
private UUID id = UUID.randomUUID();
public UUID getId() {
return id;
}
public Parent setId(UUID id) {
this.id = id;
return this;
}
}
Child
#Entity
public class Child {
#Id
#Column(nullable = false, updatable = false, columnDefinition = "uuid")
private UUID id = UUID.randomUUID();
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Parent parent;
public UUID getId() {
return id;
}
public Child setId(UUID id) {
this.id = id;
return this;
}
public Parent getParent() {
return parent;
}
public Child setParent(Parent parent) {
this.parent = parent;
return this;
}
}
Check below screenshot for how table will look in database.

Saving in one shot two parents and one child Spring JPA

I have never had a case where I want to save multiple parents and one child in one shot. In my case, I have two parent entities and one child. The two parent entities have a foreign key on the child entity.
I have an example like this ->
#Entity
#Table("parentA")
public class ParentA
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToMany(cascade = CascadeType.ALL, mappedBy="parentA")
private List<Child> child;
// Getters and Setters and some methods
}
#Entity
#Table("ParentB")
public class ParentB
{
#Column("CODE")
private Long code;
#OneToMany(cascade = CascadeType.ALL, mappedBy="parentB")
private List<Child> child;
// Getters and Setters and some methods}
#Entity
#Table("Child")
public class Child
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column("ID")
private Long ID;
#Column("parentA_ID")
private Long parentAId;
#Column("code")
private String code;//from parentB
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID", referencedColumnName = "parentA_ID")
private ParentA parentA;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "code", referencedColumnName = "code")
private ParentB parentB;
// Getters and Setters and some methods}
You just add attribute cascade = CascadeType.PERSIST to Child fields:
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn
private ParentA parentA;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn
private ParentB parentB;
// ...
}
public interface ChildRepository extends JpaRepository<Child, Integer> {
}
Then the parent entities will be saved when you save the child entity:
ChildRepository childRepo;
// ...
var child = new Child();
var parentA = new ParentA();
var parentB = new ParentB();
parentA.setChild(List.of(child));
parentB.setChild(List.of(child));
child.setParentA(parentA);
child.setParentB(parentB);
childRepo.save(child);
You can see that a single save inserted all three rows:
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener -
Name:dataSource, Connection:4, Time:52, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into parenta values ( )"]
Params:[()]
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener -
Name:dataSource, Connection:4, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into parentb values ( )"]
Params:[()]
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener -
Name:dataSource, Connection:4, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into child (parenta_id, parentb_id) values (?, ?)"]
Params:[(3,3)]

How to update an entity using #onetomany relationship in JPA

I created an entity using a tree structure and parent child relations using #ManyToOne and #OneToMany annotations. However only changes I made to a parent of an entity are processed in the database.
My entity class looks like this:
#Entity
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class OKR {
#Id
#Column(name = "ID")
#GeneratedValue
private UUID id;
#NotBlank
private String name;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "parentid", referencedColumnName = "id")
private OKR parent;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<OKR> children;
private boolean isRoot;
public OKR(String name, OKR parent, List<OKR> children){
this.name = name;
this.children = children;
if (this.children==null){
this.children = new ArrayList<>();
}
this.parent = parent;
if (parent == null||parent.equals(new UUID(0,0))){
isRoot = true;
}else{
isRoot = false;
}
}
protected OKR(){
isRoot = true;
children = new ArrayList<>();
}
When I update an OKR by changing its parent, the parent OKR is updated as well. However when I update an OKR by only adding a child, the child OKR does not get updated. I'm fairly new to JPA and I also noticed that there is no table for children inside my database. So my question is what is the easiest way to update all relations when only updating the children of an entity?

Hibernate deletion referential integrity constraint violation on many to many association

I am trying to use Hibernate to remove an entity however I get an error: Cannot delete or update a parent row: a foreign key constraint fails
The setup is that I have an abstract class A and two classes (B and C) which extend A. B contains a list of C's (unidirectional relationship). And there is a function to delete A by its ID.
Note: Stuff has been removed for brevity.
#Entity
public class B extends A {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
joinColumns = #JoinColumn(name = "B_A_id"),
inverseJoinColumns = #JoinColumn(name = "C_A_id"))
List<C> cList;
}
#Entity
public class C extends A {
(no reference to B)
}
The issue is that when the deleteAByFixedId is called where A is a C, it tries to delete the C before it deletes the B which references it and therefore I get a foreign key constraint failure.
What am I doing wrong?
The answer will still be updated.
Links:
The best way to use the #ManyToMany annotation with JPA and Hibernate
Hibernate Inheritance Mapping
#ManyToMany
Unidirectional example:
User.java
#Entity
public class User {
#Id
#GeneratedValue
#Column(name = "user_id")
private long id;
...
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public void addRoles(Role role) {
roles.add(role);
}
public void removeRoles(Role role) {
roles.remove(role);
}
}
Role.java
#Entity
public class Role {
#Id
#GeneratedValue
#Column(name = "role_id")
private int id;
#Column(name = "role")
private String role;
}
Bidirectional example:
Trader.java:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#ToString(exclude = "stockmarkets")
#Table(name = "trader")
public class Trader {
#Id
#GeneratedValue
#Column(name = "trader_id")
private Long id;
#Column(name = "trader_name")
private String traderName;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "TRADER_STOCKMARKET",
joinColumns = { #JoinColumn(name = "trader_id") },
inverseJoinColumns = { #JoinColumn(name = "stockmarket_id") })
private Set<Stockmarket> stockmarkets = new HashSet<>();
/*
We need to add methods below to make everything work correctly
*/
public void addStockmarket(Stockmarket stockmarket) {
stockmarkets.add(stockmarket);
stockmarket.getTraders().add(this);
}
public void removeStockmarket(Stockmarket stockmarket) {
stockmarkets.remove(stockmarket);
stockmarket.getTraders().remove(this);
}
}
Stockmarket.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#ToString(exclude = "traders")
#Table(name = "stockmarket")
public class Stockmarket{
#Id
#GeneratedValue
#Column(name = "stockmarket_id")
private Long id;
#Column(name = "stockmarket_name")
private String stockmarketName;
#ManyToMany(mappedBy="stockmarkets")
private Set<Trader> traders = new HashSet<>();
/*
We need to add methods below to make everything work correctly
*/
public void addTrader(Trader trader) {
traders.add(trader);
trader.getStockmarkets().add(this);
}
public void removeTrader(Trader trader) {
traders.remove(trader);
trader.getStockmarkets().remove(this);
}
}

access many to many relation in spring

I have a class called Tag:
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "tags")
private Set<Post> posts = new HashSet<>();
...
}
And a class called Post
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "post_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tag> tags = new HashSet<>();
...
}
It creates another table named post_tags.
How can I write a Controller to access that table as it is not similar a repository?
Is there more easy and convenient way to implement ManyToMany relationship ?
My pom.xml
You don't need to access that relation table manually. You can load load all Tag entities, and then load all the referenced Post entities.
The relation table is enterily managed by your ORM frameork.
But, if you still want to access the relation table, you can use native queries in your Spring Data JPA repository, e.g.
#Query(value="select post_id, tag_id from post_tags", nativeQuery=true)
List<PostTag> loadPostTags();
PostTag class is not a jpa-managed entity and must match the structue of the returned table:
public class PostTag {
private long postId;
private long tagId;
// getter, setter
}
Use this way
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "post_id") })
private Set<Post> posts = new HashSet<>();
...
}
#Entity
#Table(name = "posts")
public class Post {
#Id
#Column(name = "post_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long postId;
...
}

Resources