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?
Related
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);
}
I have an Entity Recipe with a relationship OneToMany with Ingredients.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL) // se eliminiamo la Recipe eliminiamo anche notes
private Notes notes;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "recipe")
private Set<Ingredient> ingredients;
#ManyToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "recipe_category",
joinColumns = #JoinColumn(name = "recipe_id"),
inverseJoinColumns = #JoinColumn(name = "category_id"))
private Set<Category> categories;
...getter and setter...
}
And an Entity Ingredient:
#Entity
public class Ingredient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
private int amount;
#ManyToOne
private Recipe recipe;
...getter and setter...
}
In order to test it I have used a controller to insert and retrieving all row:
#GetMapping({"","/"})
public List<Recipe> allRecipe() {
return recipeRepository.findAll();
}
#GetMapping("/insert")
public Recipe addRecipe() {
Set<Ingredient> ingredients = new HashSet<>();
ingredients.add(new Ingredient("ingredient-"+Math.random(), 10));
Recipe newRecipe = new Recipe("Recipe-"+Math.random(),
null, ingredients, null);
return recipeRepository.save(newRecipe);
}
The repository is a JPA Repository.
I do not have any errors, but when I try to retrieve an object I get no ingredients even though they are saved on the table (but with recipe_id = null).
How can I solve this problem?
Initialize your ingredients as
#OneToMany(cascade = CascadeType.ALL, mappedBy = "recipe")
private Set<Ingredient> ingredients = new HashSet<>();
Change your your controller to,
#GetMapping("/insert")
public Recipe addRecipe() {
Ingredient ingredient = new Ingredient("ingredient-"+Math.random(), 10));
Recipe newRecipe = new Recipe("Recipe-"+Math.random(),
null, null); //constructor without ingredient arg
newRecipe.getIngredients.add(ingredient);
ingredient.setRecipe(newRecipe);
return recipeRepository.save(newRecipe);
}
I struggle for a week with the following problem:
How is it possible to delete a child entity through a repository without modifying the List on the owning (parent) side of the relation?
Thanks in advance.
I am hoping for some answers!
The child class:
#Entity
#Table(name = "child")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
private Parent parent;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
And the parent class:
#Entity
#Table(name = "parent")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Child> children = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<Child> getChildren() {
return children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
public Parent addChild(Child child) {
this.children.add(child);
child.setParent(this);
return this;
}
public Parent removeChild(Child child) {
this.children.remove(child);
child.setParent(null);
return this;
}
}
And here the test:
#Test
#Transactional
public void testParentToChildRelationShip() {
Parent parent = new Parent();
Child child = new Child();
parent.addChild(child);
parent.addChild(new Child());
parent.addChild(new Child());
parent.addChild(new Child());
parentRepository.save(parent);
Assertions.assertThat(parentRepository.count()).isEqualTo(1L);
Assertions.assertThat(childRepository.count()).isEqualTo(4L);
childRepository.delete(child);
Assertions.assertThat(parentRepository.count()).isEqualTo(1L);
// fails
Assertions.assertThat(childRepository.count()).isEqualTo(3L);
parentRepository.delete(parent.getId());
Assertions.assertThat(parentRepository.count()).isEqualTo(0L);
Assertions.assertThat(childRepository.count()).isEqualTo(0L);
}
The test would work if I insert before deleting the child,
child.getParent().removeChild(child);
but I want to avoid calling this.
Is there a way to make it work with just calling the Child-JPA-Repository.delete method? Or other annotations that I missed?
Since child has association with parent you are facing this issue, you need to remove the link between child and parent either using
parent.removeChild(child);
or
child.getParent().removeChild(child);
Remove these lines from your parent class and also setter and getter of children
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Child> children = new HashSet<>();
I think you can remove child mapping from your parent class so you can easily delete the child row using ChildRepository delete() method but problem is that you have to save your child manually using ChildRepository save(). You can not save child object with parent object using ParentRepository. Change your Test code like below for saving child and parent
Parent parent = new Parent();
Parent parent = parentRepository.save(parent);
Child child = new Child();
child.setParent(parent);
childRepository.save(child);
Hibernate lazy loading is not working in my code. It loads the entire data even it is specified as FetchType LAZY
#Transactional(propagation = Propagation.NEVER)
public OrderItem getItem(String itemId) throws Exception {
OrderItem item = itemDao.find(OrderItem.class, Integer.parseInt(itemId));
if (item == null) {
throw new Exception(502, "We are unable to load item for #" + itemId);
}
return item;
}
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name = "id_order_detail")
#Fetch(value= FetchMode.JOIN)
#JsonManagedReference
private Set<OrderItemStateChangeEntry> itemStateHistory;
I could not able to lazy load the contents. There is no foreign key constraint set in the db. And its not possible to set as the many parent data not present in the system.
Can somebody help me on this
Update
Added my class and reference. But lazy load work
#Entity
#Table(name = "ps_orders")
#AttributeOverrides({
#AttributeOverride(name="id",column=#Column(name="id_order")),
#AttributeOverride(name="createTime",column=#Column(name="date_add")),
#AttributeOverride(name="updateTime",column=#Column(name="date_upd"))
})
public class Order extends BaseEntity{
#Column(name = "id_carrier")
private Integer carrier = 0;
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(fetch = FetchType.LAZY,cascade={CascadeType.PERSIST, CascadeType.MERGE}, mappedBy="order")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private Set<OrderStateChangeEntry> orderHistory;
//Getters and Setters
}
#Entity
#Table(name = "ps_order_history")
#EnableBouncerProfile
public class OrderStateChangeEntry implements java.io.Serializable{
public OrderStateChangeEntry(){}
public OrderStateChangeEntry(Order order){
this.order = order;
}
#Id
#Column(name = "id_order_history")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="id_order", nullable=false)
#JsonBackReference
private Order order;
//Getters and Setters
}
It is because of your
#Fetch(value= FetchMode.JOIN)
it disables the lazy loading ...
As you specify the fetch mode in your #OnetoMany relationship, i would say that you can simply remove that line above.
how to add one-to-one mapping for the self entity. Like in this example. I want to have parent-child relationship for the Person itself.
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="personId")
private int id;
#OneToOne
#JoinColumn()
private Person parentPerson;
}
Here is example of bidirectional self mapping #OneToOne (I change column names to SQL notation):
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="person_id")
private int id;
#OneToOne
#JoinColumn(name = "parent_person_id")
private Person parentPerson;
#OneToOne(mappedBy = "parentPerson")
private Person childPerson;
}
But, I don't understand why you want to use #OneToOne in this case.
I am using it like this:
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "PARENT_ID", nullable = true)
private Person parent;
In order to add parent from your service layer, you need to already have at least one Person in the database.
Lets presume you do. Then create a new person. For e.g.:
#Transactional
public void createPerson() {
Person parent = //get your parent
Person child = new Person();
if (parent != null) {
child.setParent(parent);
}
}
If this is what you mean..