JPA entity relationship with itself and cascade delete - java

I have entity object which has relationship with itself in two forms:
entity refer to list of that entities
entity refer to entity (mainOrder), which is superior current enitity
Java class for order:
#Entity
#Table(name = "shop_order")
public class ShopOrder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", columnDefinition = "serial")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "main_id",
nullable = true)
private ShopOrder mainOrder;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "id") // I am not sure here should be id or mainOrder
private Set<ShopOrder> subOrders = new HashSet<>();
// some others columns, and getters and setters
}
Can you tell me how to ensure that entity for cascade remove? I need if some order will be removed, also will be removed its suborders (it means orders which have set that removed order as mainOrder)

mappedBy is the attribute name in the class ShopOrder that is the back reference to ShopOrder.
Cascade can be defined in the annotation attribute cascade
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainOrder", cascade = CascadeType.REMOVE)
private Set<ShopOrder> subOrders = new HashSet<>();
Read more about Cascading in the Hibernate manual:
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#pc-cascade

Related

Hibernate Multiple #OneToMany bound to same entity type

I have yet another #OneToMany question. In this case, I'm trying to model a person having a list of excluded people they shouldn't be able to send items to. This is a Spring Boot app using JPA.
In the code below, the exclusions list populates properly but the excludedBy List does not. Because of this, I believe that is causing the deletion of a Person that is excluded by another person to fail because the Exclusion in excludedBy is not mapped on the object properly.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
...
#OneToMany(mappedBy = "sender", cascade = { CascadeType.ALL })
List<Exclusion> exclusions = new ArrayList<>();
//This is not getting populated
#JsonIgnore
#OneToMany(mappedBy = "receiver", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
...
}
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
#ManyToOne
#JsonIgnore
Person sender;
#ManyToOne
Person receiver;
...
}
I would expect that this would have mapped the bidirectional relationship properly and as such the excludedBy List would be populated as well.
Any wisdom on this matter would be great!
1 - An #Id is by default not nullable, not required:
#Column(nullable = false)
2 - There is no need for an #Id in this class. Both sides of the exclusion are together unique. Not needed:
#Id
#GeneratedValue
Long id;
3 - An "Exclusion" requires both an excludedBy and an excluded, give them names that match and they are your #Id. It is a 2 way ManyToMany relationship.
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excludedBy;
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excluded;
}
Entity Exclusion always knows both sides of the story.
#ManyToMany(mappedBy = "excludedBy", cascade = { CascadeType.ALL })
List<Exclusion> excluded = new ArrayList<>();
#ManyToMany(mappedBy = "excluded", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
Tip: JSON DTOs shouldn't be defined in your JPA DTOs, otherwise you can't change your internal data model independently of your external API model.
I had this problem in the past. Your key problem ist that your ORM Mapper hibernate does not know which of your database entries need to be assinged to exclusions and which are assiged to excludedBy. You need a discriminator and add the constraint in your select. I would propose a solution that looks something like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 0")
private Set<Exclusion> exclusions;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 1")
private Set<Exclusion> excludedBy;
the value isExcludedBy needs to be a database column, part of your Entity and set in your code manually.
I think you also need to use Set instead of List when having multiple collections in one Entity. https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/

Spring Data JPA - Hibernate - OneToMany, ManytoOne bidirectional

Have 2 Entities: Orders and Products. 1 Order can have Many Products and Many Products can belong to 1 Order (Each Product only belongs to 1 Order).
With unidirectional association at Order Entity, I am able to retrieve product details when performing orderRepo.findAll(); In similar fashion, need order details when performing productRepo.findAll();
Tried code:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "order_details")
public class OrderData {
#Id
#Column(name = "order_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;
#NotNull
#Column(name = "customer_name", nullable = false)
private String customerName;
#OneToMany(mappedBy = "productId", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ProductData> products;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "product_details")
public class ProductData {
#Id
#Column(name = "product_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId;
#NotNull
#Column(name = "product_name", nullable = false)
private String productName;
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", nullable = false)
private OrderData orderData;
}
While inserting at products; we are getting error: "insert or update on table violates foreign key constraint jpa"
While performing productRep.findAll(): infinite loop for hibernate select queries
Tried #JsonIgnore. This not returning child or parent elements.
Tried #JsonManagedReference vs #JsonBackReference - still no luck.
Please guide me on this
The mappedBy attribute points to the wrong field:
#OneToMany(mappedBy = "productId", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ProductData> products;
This must be the back reference:
#OneToMany(mappedBy = "orderData", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ProductData> products;

How to remove children from parent entity record in JPA?

I have Product entity and ProductRating entity, each Product can have many ProductRatings. When Product is deleted I want to have associated ratings deleted too, but nothing works so far (also orphanRemoval set to true)...
Classes:
#Getter
#Setter
#Entity
#Table(name = "PRODUCT")
public class Product extends AbstractEntity<Long> {
#Column(nullable = false)
private String name;
private String description;
#Column(nullable = false)
#Min(value = 0)
private Float cost;
#OneToMany(mappedBy = "product",
orphanRemoval = true, cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER)
//#OnDelete(action = OnDeleteAction.CASCADE)
#Fetch(value = FetchMode.SELECT)
private Set<ProductRating> productRatings;
}
#Getter
#Setter
#Entity
#Table(name = "PRODUCT_RATING")
public class ProductRating extends Rating {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "product_id")
#NotNull(message = "Rating must be in context of Product")
private Product product;
}
After Product deletion ratings stay with deleted Product's ID
AbstractEntity implementation:
#Getter
#Setter
#MappedSuperclass
public abstract class AbstractEntity<I> implements Serializable {
private static final long serialVersionUID = 1700166770839683115L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", unique = true, nullable = false)
private I id;
}
In the #OneToMany relation you need to add the cascade type delete: cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
Or if you don't mind having all cascade types you can just put: cascade = CascadeType.ALL
EDIT:
Also check the name of the Product primary key in the database.
It should match the defined in the #JoinColumn annotation of ProductRating
The default database field for the attribute id of the Product class would be product_id.
However you have defined the id in AbstractEntity as name = "ID" so the #JoinColumn should be something like: #JoinColumn(name = "ID")
My alternative approach to fix this problem is to:
On parent-side relation create method with #PreRemove annotation
in this method iterate over collection with #[One/Many]ToMany annotation and call delete(obj) method for corresponding repository on child
On child-side relation create method with #PreRemove annotation
In this method set parent to null

Hibernate identity generator issue

I am having a particular issue when trying to save a collection of objects with hibernate. It seems that when I have more than one object of the same type, hibernate fails to generate the identifier, so I get a org.hibernate.NonUniqueObjectException .
Example:
App1 --> urls
{strApplicationId:1;URLTypeEntity{strCode:1,strDescription:Reply},strURL:www.address1.com},
{strApplicationId:1;URLTypeEntity{strCode:1,strDescription:Reply},strURL:www.address2.com},
{strApplicationId:1;URLTypeEntity{strCode:2,strDescription:Home},strURL:www.address3.com}
If I do not have two URLs with the same URLTypeEntity on the collection, the error is not triggered
#Entity
#Table(name = "tbl_urls")
public class URLEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="intCode")
private Integer intCode;
private String strApplicationID;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "intType", referencedColumnName = "intCode")
private URLTypeEntity objURLType;
private String strURL;
}
#Entity
#Table(name = "tbl_applications")
public class ApplicationEntity
{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "strApplicationID")
private List<URLEntity> colURLs;
}
ApplicationEntity must also have an id.
The solution was changing the CascadeType from ALL To Merge
#OneToMany(cascade = CascadeType.ALL, mappedBy = "strApplicationID")
Changed To
#OneToMany(cascade = CascadeType.MERGE, mappedBy = "strApplicationID")

Update association on child delete

I have three entity classes.
Product, Category, and SubCategory.
A Category has a OneToMany relation with SubCategory
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "category_id")
private Long categoryId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "category_id")
private List<SubCategory> subCategories;
}
The product is assocciated with a Category and one of its SubCategories
#Entity
#Table(name = "PRODUCTS")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = ("id"))
private Long id;
#ManyToOne
#JoinColumn(name = "category", unique = false, nullable = true, insertable = true, updatable = true)
private Category category;
#ManyToOne
#JoinColumn(name = "sub_category", unique = false, nullable = true, insertable = true, updatable = true)
private SubCategory subCategory;
}
now if I delete a Category, all its SubCatogries are deleted, but I also want the associations in Product to be updated to null. I thought of manually fetching all the products with the associated deleted Category and updating them manually, but is there a way to handle this with JPA annotations?
This update from JPA will be very inefficient in performance perspective.
Your table PRODUCTS has columns category abd sub_category which linked with correspond tables by foreign keys. Add to end of definition of each of these columns string 'ON DELETE SET NULL' and what you want will be done by database automatically.
The Product entity has a ManyToOne relationship with SubCategory that means that the SubCategory entity has a OneToMany relationship with Product. So, in the SubCategory class, where you have defined the OneToMany relationship, you need to mention the cascadetype = remove
#manytoone(cascade = cascadetype.remove)
Hope it solves your problem

Categories