Spring Boot JPA. Removing entity in oneToMany relationships - java

This is my entity
package com.nimesia.sweetvillas.entities;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name = "specs", schema = "crm")
public class SpecEntity extends AbsEntity {
#Id
#Column(name = "spec_id")
private #Getter #Setter String id;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "specs_translations",
schema="crm",
joinColumns = #JoinColumn(name = "spec_id"),
inverseJoinColumns = #JoinColumn(name = "translation_id")
)
private #Getter #Setter
List<TextEntity> texts;
}
As you can see there is a oneToMany relationships with TextEntity. Such relationship is generated through the specs_translations table.
Here comes the problem.
When it comes to creating the specEntity I can create also its subentities (translations, in this case). In the db, a reference in specs_translations and a record in translations (table which contains the records for textEntities) will be created. As it should be.
But when it comes to updating and removing a TextEntity from my SpecEntity, while the reference in specs_translations gets removed, the relative translation record stays.
This leads to a situation such as the one depicted in the following pics
How can I delete the record in translations when I remove its reference in specs_translations? I would like to achieve this through JPA and not through the DB.

I solved it. I just set orphanRemoval to "true".

Related

JPA Hibernate FetchType.EAGER and FetchMode.JOIN for non primary key

I have come across a very interesting scenario. I know about the n+1 problem and FetchType.EAGER and FetchMode.JOIN.
I have a parent entity School which has 2 #OneToMany children entity ie Student and Teacher. I need all 3 so using FetchType.EAGER and FetchMode.JOIN.
school entity
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import javax.persistence.*;
import java.util.Set;
#Entity
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class School {
#Id
#GeneratedValue(generator = "sequence", strategy = GenerationType.IDENTITY)
#SequenceGenerator(name = "sequence", allocationSize = 10)
int schoolId;
String schoolName;
float schoolRating;
#OneToMany(mappedBy = "school", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Set<Teacher> teachers;
#OneToMany(mappedBy = "school", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Set<Student> students;
}
Student entity
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.Date;
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Student {
#Id
#GeneratedValue(generator = "sequence", strategy = GenerationType.IDENTITY)
#SequenceGenerator(name = "sequence", allocationSize = 10)
public int studentId;
public byte studentByte;
public Date date;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "schoolId", referencedColumnName = "schoolId")
private School school;
}
Teacher entity
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.Date;
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Teacher {
#Id
#GeneratedValue(generator = "sequence", strategy = GenerationType.IDENTITY)
#SequenceGenerator(name = "sequence", allocationSize = 10)
public int teacherId;
public byte teacherByte;
public Date date;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "schoolId", referencedColumnName = "schoolId")
private School school;
}
School Repo
#Repository
public interface SchoolRepository extends JpaRepository<School, Integer> {
List<School>findBySchoolName(String schoolName);
}
if I get School object via the findById method, ie the primary key of the table.
Optional<School> schoolById = schoolRepository.findById(1);
generated SQL is a join of school, student, and teacher entity.
select school0_.schoolId as schoolid1_0_0_, school0_.schoolName as schoolna2_0_0_, school0_.schoolRating as schoolra3_0_0_, students1_.schoolId as schoolid4_1_1_, students1_.studentId as studenti1_1_1_, students1_.studentId as studenti1_1_2_, students1_.date as date2_1_2_, students1_.schoolId as schoolid4_1_2_, students1_.studentByte as studentb3_1_2_, teachers2_.schoolId as schoolid4_2_3_, teachers2_.teacherId as teacheri1_2_3_, teachers2_.teacherId as teacheri1_2_4_, teachers2_.date as date2_2_4_, teachers2_.schoolId as schoolid4_2_4_, teachers2_.teacherByte as teacherb3_2_4_ from School school0_ left outer join Student students1_ on school0_.schoolId=students1_.schoolId left outer join Teacher teachers2_ on school0_.schoolId=teachers2_.schoolId where school0_.schoolId=?
but if I find a school with some other variable which is not a primary key.
List<School> schoolByName = schoolRepository.findBySchoolName("school1");
generated SQL is 3 different hits on DB.
Hibernate: select school0_.schoolId as schoolid1_0_, school0_.schoolName as schoolna2_0_, school0_.schoolRating as schoolra3_0_ from School school0_ where school0_.schoolName=?
Hibernate: select teachers0_.schoolId as schoolid4_2_0_, teachers0_.teacherId as teacheri1_2_0_, teachers0_.teacherId as teacheri1_2_1_, teachers0_.date as date2_2_1_, teachers0_.schoolId as schoolid4_2_1_, teachers0_.teacherByte as teacherb3_2_1_ from Teacher teachers0_ where teachers0_.schoolId=?
Hibernate: select students0_.schoolId as schoolid4_1_0_, students0_.studentId as studenti1_1_0_, students0_.studentId as studenti1_1_1_, students0_.date as date2_1_1_, students0_.schoolId as schoolid4_1_1_, students0_.studentByte as studentb3_1_1_ from Student students0_ where students0_.schoolId=?
I realized that join only works if we are getting by id ie primary key, but I don't have a primary key of School. I have the name of the school which is unique and indexed and needs student entity and teacher entity also. is there a way to get them all using join in hibernate. I know if student and teacher records are more then it will be performance degradation, but in my case, it will be at most 3-4 records only. that's why I want to join them all.
It is not advisable to map more than one associated collection fields of an Entity with FetchMode.JOIN. This is to avoid Cartesian product issue. I am surprised it did a sql join even when you selected by Id
When you are fetching School other than it's Id field, hibernate does not know how many Schools you will be fetching, so if it did a join fetch rather than separate selects, it will end up with a Cartesian product issue
Say you have 10 schools, each school has 20 teachers and 400 students. If hibernate did a join it will have to bring 80,000 (10*20*400) records from db.
But since it is doing separate select, it will bring 4,210 (10 + 200 + 4000) records. Even in the case selecting by Id it is 420 records vs 8000 records
Short Answer
Do not retrieve Parent entity and more than one of its associated collections using join even if you find a way to do that because performance will be worse than multiple selects.
Update:
If you are sure that the school name is unique and there is only a few teachers per school and students size is small, you can do the following: (currently your findBySchoolName returns List<School>, you can change that to return an optional school)
#Repository
public interface SchoolRepository extends JpaRepository<School, Integer> {
#Query("SELECT s from School s left join fetch s.teachers " +
"left join fetch s.students where s.schoolName = :name")
Optional<School> findBySchoolName(String name);
}

Hibernate problem - “Use of #OneToMany or #ManyToMany targeting an unmapped class”

I have 2 entities, LCPUserDetails and LCPUserPrivilege. LCPUserDetails has a List class member, so a One to Many relationship. When I run my unit test I am getting this exception:
#Entity
#Table(name = "LCP_USER_DETAILS")
public class LCPUserDetails {
#OneToMany(orphanRemoval = true, cascade = {CascadeType.ALL},
mappedBy = "userDetails")
private List<LCPUserPrivilege> privileges= new ArrayList<>();
}
#Entity
#Table(name = "LCP_USER_PRIVILEGE")
public class LCPUserPrivilege {
#ManyToOne
#JoinColumn(name = "USER_ID")
private LCPUserDetails userDetails;
}
As Sheik Sena Reddy mentioned, you have to update your list of entities. If you don't use an xml file, you can check where you set your EntityManagerFactory and add a list of package that your EMF will scan to list your entities emf.setPackagesToScan(['my.package.to.scan']);.

Hibernate #OnDelete(action = OnDeleteAction.CASCADE) not working

I have ManyToOne mapping and I wish to delete all the child elements upon deletion of Parent. I saw a solution over here and came to know that it is not necessary to have bi-directional relation to achieve such logic but it is not working in my case.
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
#Entity
#Table(name="SubOrder")
public class SubOrder {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="order_seq")
#SequenceGenerator(name = "order_seq", initialValue=1000000000, allocationSize=1, sequenceName="order_auto_seq" )
#Column(name="sub_order_id")
private long subOrder;
#Column(name="product_name" , length=50)
private String productName;
#Column(name="Date")
private String date;
#ManyToOne()
#Cascade({CascadeType.PERSIST})
#OnDelete(action = OnDeleteAction.CASCADE)
private Order order;
public Order getOrder() {
return order;
}
//Getters and setters
Below is my parent class
#Entity
#Table(name="Orders")
public class Order {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="order_seq")
#SequenceGenerator(name = "order_seq", initialValue=1000000000, allocationSize=1, sequenceName="order_auto_seq" )
#Column(name="order_number")
private long orderNumber;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="Order_date")
private Date date= new Date();
//Getters and setters
My deletion code
Order result =session.get(Order.class, 1000000000l);
session.delete(result);
The above code deletes Order(parent) but not child elements.
My hibernate-cfg.file
<property name="hbm2ddl.auto">update</property>
What could be wrong here ?
I found out the mistake. I was relying on Hibernate to create cascading relationship while creating tables, which was not happening. I had to manually create table by specifying
`FOREIGN KEY (order_order_number) REFERENCES orders (order_number)
ON DELETE CASCADE
ON UPDATE CASCADE`
while creating suborder table in MySql. After this it started working.
Update
Or when you modify any entity better use 'hbm2ddl.auto=create' so that hibernate delete existing one and create a fresh new table with updated constraints.

How to restrict associated tables from getting considered for join when using hibernate update() on entity having multiple #ManyToOne associations

I have a Record entity that maintains a #ManyToOne relation with Product_Category table and Price_Category table. The record entity also maintains a #OneToMany association with another table called PendingRecords.
Currently this is working perfectly for hibernate save/persist, in which all the associated tables also get modified when specific conditions are met. The problem is these associated entities are also getting considered on hibernate update/delete which is not what I intended to do. I want to restrict these entites from getting considered and allow change only on insert.
Following are the entity class mappings
Record.java
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
#Entity
#Table(name = "ITEM_RECORDS")
public class Record {
-------------------------
#ManyToOne(optional=false)
#JoinColumn(name = "PRICE_CATEGORY", referencedColumnName="price_category", nullable=false)
private PriceCategory price_category;
#ManyToOne(optional=false)
#JoinColumn(name = "PRODUCT_CATEGORY", referencedColumnName="product_category", nullable=false)
private ProductCategory product_category;
#OneToOne(mappedBy="record", cascade = CascadeType.ALL)
private PendingRecords pendingRecord;
----------------------
(getter setter methods)
-----------------------
Following are the associated tables
PriceCategory.java
#Entity
#Table(name = "PRICE_CATEGORY")
public class PriceCategory{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "PRICE_CATEGORY")
private String price_category;
#Column(name = "DESCRIPTION")
private String description;
----------------------------
(getter-setters)
----------------------------
ProductCategory.java
#Entity
#Table(name = "PRODUCT_CATEGORY")
public class ProductCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "PRODUCT_CATEGORY")
private String product_category;
#Column(name = "DESCRIPTION")
private String description;
----------------------------
(getter-setters)
----------------------------
and finally,
PendingRecords.java
#Entity
#Table(name = "PENDING_RECORDS")
public class PendingRecords{
#Id
#Column(name = "PENDING_RECORD_NO")
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign",parameters=#Parameter(name="property", value="record"))
private Long pending_record_id;
----------------------
#OneToOne
#PrimaryKeyJoinColumn
private Record record;
-------------------------
(getters-setters)
-------------------------
The entity associations are working fine when I perform the insert (hibernate persist/save). The problem is when I try to update the Record, hibernate is trying to update all the associated entries also like
select
this_.RECORD_NO as REC_REC1_1_3_,
this_.PRICE_CATEGORY as PRICE_CA10_1_3_,
this_.PRODUCT_CATEGORY as PRODUCT11_1_3_,
pricecatego2_.id as id1_0_0_,
pricecatego2_.PRICE_CATEGORY as PRICE_CAT2_0_0_,
pricecatego2_.DESCRIPTION as DESCRIPT3_0_0_,
productcat3_.ID as ID1_3_1_,
productcat3_.DESCRIPTION as DESCRIPT2_3_1_,
productcat3_.PRODUCT_CATEGORY as PRODUCT_3_3_1_,
pendingrec4_.PENDING_RECORD_NO as REC_REC1_2_2_,
---------------------------
from
ITEM_RECORDS this_
inner join
PRICE_CATEGORY pricecatego2_
on this_.PRICE_CATEGORY=pricecatego2_.PRICE_CATEGORY
inner join
PRODUCT_CATEGORY productcat3_
on this_.PRODUCT_CATEGORY=productcat3_.PRODUCT_CATEGORY
left outer join
PENDING_RECORDS pendingrec4_
on this_.PENDING_RECORD_NO=pendingrec4_.PENDING_RECORD_NO
where
this_.PENDING_RECORD_NO=?
What should be done to prevent considering these entities for join operations while updating Record entity? Because this is an additional overhead and also generates ClassCastException. I need associated entites to be changed only during persit or save and not during update or delete.
I am getting the following error during update
Servlet.service() for servlet dispatcher threw exception: java.lang.ClassCastException: com.myapps.models.PriceCategory cannot be cast to java.io.Serializable
Should I specify CascadeType? Or should i use hibernate CascadeType instead of JPA's which I am currently using for PendingRecords? Or is there another way? Please help me resolve this.
Thanks in advance.
I need associated entites to be changed only during persit or save
and not during update or delete
If, once the related entities are set at persist time, they should never be overwritten with another entity instance, use #JoinColumn(updatable=false).
Note that because of Hibernate's flush operation order, it the related entities do not already exist, you might need to save them first, flush the EntityManager, and only then save the top-level entity.
What should be done to prevent considering these entities for join
operations while updating Record entity?
Try #ManyToOne(fetch=LAZY). I am not entirely sure this will work during a merge, though, as there are many reasons for Hibernate to fetch related entities during a merge. If it doesn't work, you might need to write a custom update query.

jpa mapped superclass without id

I'm curious about the following problem. I've two entites, A and B. It stores almost the same information (for example, a name - in real life it's more complex), but the joins, and the foreign keys differs.
Can I do a mapped superclass, without an Id. And class A and class B, extending the mapped superclass, containing only the Id attribute?
For example:
import lombok.Getter;
import lombok.Setter;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToMany;
#MappedSuperclass
#Getter
#Setter
#Data
class superClass {
#Column(name = "name")
private String name;
}
#Entity
#Table(name = "A")
#Data
class A extends superClass {
#Id
#OneToMany
#JoinColumn(name = "id", referencedColumnName = "referencedName")
private SomeClass id;
}
#Entity
#Table(name = "B")
#Data
class B extends superClass {
#Id
#OneToOne
#JoinColumn(name = "id", referencedColumnName = "referencedName")
private SomeOtherClass id;
}
Would it be valid by the JPA? I did read the mappedSuperClass's JavaDocs, and says nothing about it. I would said, that it is valid - but the IntelliJ Idea says, that the super class has to have an Id attribute. I didn't find anything on the internet about this.
edit: sorry, I missed it. I left the Entity annotation on the superClass, and that's why the Idea signed the error. I removed that, and the error disappeared. But I'm not sure, that this is valid, though.
yes, there is no requirement that a MappedSuperclass have anything in it. It is just providing extra annotations for the subclasses.
Yes it is valid. Anyway your superclass will not appear as a table in the DB.

Categories