JPA delete #ManyToOne Entity on EAGER collection - spring

Very simple problem, but it looks like is impossible to achieve what I want
The entities:
public class C {
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "column")
private Set<B> cards = new HashSet<>();
}
public class B {
#ManyToOne(fetch = FetchType.EAGER, optional = false, cascade = CascadeType.DETACH)
#JsonIgnore
#JoinColumn(name="column_id", nullable = false)
private C column;
}
#Repository
public interface BRepository extends JpaRepository<B, Long> {
}
I want to delete the B entity without use the C Repository.
if I do something like:
final C column = columnService.create(board, new C(board, "column name", 1)); //works
final B card = cardService.create(column, new B(column, "card name", 2)); //works
bRepository.delete(card); //NOTHING HAPPENS
Absolutely nothing happens on delete query, no log, data isn't removed from DB, nothing.... doesn't matter if I'm within or out a #transaction.
WHAT I'VE TRIED:
1 - if i change Set cards to FetchType.LAZY, the delete works, [but i really wanted it to be eager]
2 - if create a custom query on repository like:
#Modifying
#Query("DELETE FROM Card c where c.id = :id")
public void deleteById(#Param("id") long id);
then the delete works BUT, I've EntityListeners for this entity, and as per JPA documentation it wont work on custom query... so i need this component working
Is there a way to delete the ONE side of relationship with EAGER fetch without custom query and without loading the other side of relationship?

Related

Hibernate generates select after update

I have an issue with Hibernate in spring-boot.
So, mapping in spring-boot is like this:
Class A
#ManyToOne(fetch = FetchType.LAZY)
private C c;
#OneToMany(cascade = CascadeType.PERSIST,
mappedBy = "a", fetch = FetchType.LAZY)
private List<B> b;
Class B
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a", nullable = false)
#JsonIgnore
private A a;
Now when I do update (repositoryA.save(a)) of object of class A via JpaRepository<A, Integer> with value of b=null, hibernate generates this queries:
update a
select b from B where aId = ?
So why is this second select query needed? I tried to remove CascadeType.PERSIST but the result is same.
I don't what this, because the list can have many objects and it can affect the performance.
UPDATE:
Controller:
#PutMapping("/edit")
public A editA(#Valid #RequestBody A a, #RequestParam String idC){
return serviceA.editA(a, idC);
}
Service:
#Transactional
public A editA(A a, String idC){
C c = serviceC.getById(idC);
a.setC(c);
return repositoryA.save(a);
}
You should notice that save method either updates or creates a record depending on whether it already exists or not. If you don't want to the select afterwards, maybe try just a plain "update"?

JPA - deleteBy query not working, orphanRemoval=true not working

I am unable to understand why JPA delete is not working. Here is my parent entity:
public class RoleEntity {
//...other attributes like name etc.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name = "role_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
private Set<RoleExtEntity> extensions;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "file_id", referencedColumnName = "id", nullable = false)
private FileEntity fileEntity;
}
RoleRepository:
#Repository
public interface RoleRepository extends JpaRepository<RoleEntity, Long> {
#Transactional
int deleteByFileEntityAndName(FileEntity fileEntity, String roleName);
}
I am trying to delete the RoleEntity using FileEntity and RoleName attributes. The delete query returns val = 1 however when I do a findBy again, it gives me the record instead of null (as I think the record should be deleted, both parent and child should be deleted).
FileEntity fileEntity = fileRepository.findByFolderId(id);
RoleEntity roleToBeDeleted = roleRepository.findByFileEntityAndName(fileEntity, roleName);
int val = roleRepository.deleteByFileEntityAndName(fileEntity, roleName);
RoleEntity doesroleExistEntity = roleRepository.findByFileEntityAndName(fileEntity, roleName);
I have tried out various solutions mentioned on this platform like by using:
orphanRemoval = true
#Transactional annotation
flush()
CascadeType.ALL
However, they don't seem to work. Can someone please let me know what I am doing incorrectly here? Thanks!
Update: The issue was that I was calling a wrong method from a persistence service in my code. That method was a readOnlyTransaction() which didn't allow me to do the delete so I had to use another method withTransaction() that solved my issue.
Other database query is logged when you call I think service method?
Yot call the jpa delete method.
JPA Method roleRepository.findByFileEntityAndName(fileEntity, roleName);
return something maybe try show check.

How to write query spring boot

I have classe notification has attribut receiver with type User like this :
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "sender", referencedColumnName = "id")
#JsonIgnore
private User sender;
and user has attribut bed :
#ManyToOne
(fetch = FetchType.LAZY)
#JoinColumn(nullable = false, name = "idBed")
#JsonIgnore
private Bed idBed;
and bed has attribut room :
#ManyToOne
(fetch = FetchType.LAZY)
#JoinColumn(nullable = false, name = "idroom")
#JsonIgnore
private Room room;
and room has attribut :
#ManyToOne
(fetch = FetchType.LAZY)
#JoinColumn(nullable = false, name = "idCarePost")
#JsonIgnore
private CarePost carePost;
please need to read all this data please how can i do it ?
You can use JPARepository. It will give the CRUD features out of the box you don't need to write any query. For example, if you use the save(object) method in the service class it will save the object no need to write the insert query.
If you want to write your custom query in the repository you can write by using #Query("write your custom query", nativeQuery = true) annotation.
#Repository
public interface BlogRepository extends JpaRepository<Post, Integer> {
#Query(value = "select * from posts p where p.author like %:value%", nativeQuery = true)
List<Post> findByAuthor(String value);
}
The above example is a way to write a native query using JPARepository.
You have jpa repository method as below.
Find user by some attribute as below.
List<User> findByName();
Find user by bed as below.
List<User> findByBed( bed object);
Find user by with continue object.
List<User> findByBedRoomCarePostName(string
carePostName);

A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance - Spring and Lombok

I am getting this A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance error with my oneToMany relationship when trying to update my child element (report). Although I see this question asked a few times here, I haven't been able to make my code to work with them and I now feel it may be an issue with me using Lombok perhaps, since most of the answers here mention about changes on the hashcode and equals methods, which are abstracted away by Lombok? I tried to remove Lombok to try without it but then I got a bit confused on what to do next. If I could get some guidance on how to fix this issue within my original Lombok implementation please.
#Entity
#Table(name = "category")
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "category_title", nullable = false)
private String title;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> report;
public Category(UUID id, String title) {
this.id = id;
this.title = title;
}
}
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "report")
#Data
public class Report {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "report_title", nullable = false)
private String reportTitle;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
public Report(UUID id) {
this.id = id;
}
}
#Override
public ReportUpdateDto updateReport(UUID id, ReportUpdateDto reportUpdateDto) {
if (reportRepository.findById(id).isPresent()) {
Report existingReport = reportRepository.findById(id).get();
existingReport.setReportTitle(reportUpdateDto.getTitle());
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
Category category = new Category(existingCategory.getId(), existingCategory.getTitle());
existingReport.setCategory(category); // This is needed to remove hibernate interceptor to be set together with the other category properties
Report updatedReport = reportRepository.save(existingReport);
updatedReport.setCategory(category); // This is needed to remove hibernate interceptor to be set together with the other category properties
ReportUpdateDto newReportUpdateDto = new ReportUpdateDto(updatedReport.getId(),
updatedReport.getReportTitle(), updatedReport.getCategory());
return newReportUpdateDto;
} else {
return null;
}
}
Thank you very much.
Fast solution (but not recommended)
The error of collection [...] no longer referenced arrises in your code beacuse the synchronization between both sides of the bidiretional mapping category-report was just partially done.
It's important to note that binding the category to the report and vice-versa is not done by Hibernate. We must do this ouserselves, in the code, in order to sync both sides of the relationship, otherwise we may break the Domain Model relationship consistency.
In your code you have done half of the synchronization (binding the category to the report):
existingReport.setCategory(category);
What is missing is the binding of the report to the category:
category.addReport(existingReport);
where the Category.addReport() may be like that:
public void addReport(Report r){
if (this.report == null){
this.report = new ArrayList<>();
}
this.report.add(r);
}
Recommended Solution - Best practice for synchronizing both sides of the mapping
The suggested code above works, but it is error prone as the programmer may forget to call one of the lines when updating the relationship.
A better approach is to encapsulate that sychronization logic in a method in the owning side of the relationship. And that side is the Category as stated here: mappedBy = "category".
So what we do is to encapsulate in the Category.addReport(...) all the logic of cross-reference between Category and Report.
Considering the above version of addReport() method, what is missing is adding r.setCategory(this).
public class Category {
public void addReport(Report r){
if (this.reports == null){
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}
}
Now, in the updateReport() it is enough to call the addReport() and the commented line bellow can be deleted:
//existingReport.setCategory(category); //That line can be removed
category.addReport(existingReport);
It is a good practice including in Category a removeReport() method as well:
public void removeReport(Report r){
if (this.reports != null){
r.setCategory = null;
this.reports.remove(r);
}
}
That is the code of Category.java after the two methods were added:
public class Category {
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;
//Code ommited for brevity
public void addReport(Report r){
if (this.reports == null){
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}
public void removeReport(Report r){
if (this.reports != null){
r.setCategory = null;
this.reports.remove(r);
}
}
}
And the code for updating a report category now is this:
public ReportUpdateDto updateReport(UUID id, ReportUpdateDto reportUpdateDto) {
if (reportRepository.findById(id).isPresent()) {
Report existingReport = reportRepository.findById(id).get();
existingReport.setReportTitle(reportUpdateDto.getTitle());
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
existingCategory.addReport(existingReport);
reportRepository.save(existingReport);
return new ReportUpdateDto(existingReport.getId(),
existingReport.getReportTitle(), existingReport.getCategory());
} else {
return null;
}
}
A good resource to see a practical example of synchronization in bidirectional associations: https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/
Lombok and Hibernate - not the best of the combinations
Though we can not blame Lombok for the error described in your question, many problems may arrise when using Lombok alongside with Hibernate:
Properties being loaded even if marked for lazy loading...
When generating hashcode(), equals() or toString() using Lombok, the getters of fields marked as lazy are very likelly to be called. So the programmer's initial intention of postponing some properties loading will no be respected as they will be retrieved from the database when one of hascode(), equals() or toString() is invoked.
In the best case scenario, if a session is open, this will cause additional queries and slow down your application.
In the worst case scenarios, when no session is available, a LazyInitializationException will be thrown.
Lombok's hashcode()/equals() affecting the bevahior of collections
Hibernate uses hascode() and equals() logic to check if a object is order to avoid inserting the same object twice. The same applies to removing from a list.
The way Lombok generates the methods hashcode() and equals() may affect hibernate and create inconsistent properties (especially Collections).
See this article for more info on this subject: https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/
Lombok/Hibernate integration in a nutshell
Don't use Lombok for entity classes. Lombok annotations you need to avoid are #Data, #ToString, and #EqualsAndHashCode.
Off-topic - Beware of delete-orphan
In Category, the #OneToMany mapping is defined with orphanRemoval=true as bellow:
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;
The orphanRemoval=true means that when deleting a category, all the reports in that category will be deleted as well.
It is important to assess if that is the desired behavior in your application.
See an example of the SQLs hibernate will execute when calling categoryRepository.delete(category):
//Retrieving all the reports associated to the category
select
report0_.category_id as category3_1_0_,
report0_.id as id1_1_0_,
report0_.id as id1_1_1_,
report0_.category_id as category3_1_1_,
report0_.report_title as report_t2_1_1_
from
report report0_
where
report0_.category_id=?
//Deleting all the report associated to the category (retrieved in previous select)
delete from
report
where
id=?
//Deleting the category
delete from
category
where
id=?
Just an update based on the accepted answer to avoid a StackOverflow and circular loop that came up after the changes.
I had to create a new Category object to remove the reports inside it within my return dto, otherwise as the category contains that same report, that again contains that category and so on, the infinite loop could be seen on my response.
#Override
public ReportUpdateDto updateReport(UUID id, ReportUpdateDto reportUpdateDto) {
if (reportRepository.findById(id).isPresent()) {
Report existingReport = reportRepository.findById(id).get();
existingReport.setReportTitle(reportUpdateDto.getTitle());
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
Category category = new Category(existingCategory.getId(), existingCategory.getTitle());
existingCategory.addReport(existingReport);
reportRepository.save(existingReport);
return new ReportUpdateDto(existingReport.getId(),
existingReport.getReportTitle(), existingReport.getRun_date(),
existingReport.getCreated_date(), category);
} else {
return null;
}
}
So added this part:
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
Category category = new Category(existingCategory.getId(), existingCategory.getTitle());
existingCategory.addReport(existingReport);
As if I have something like
Category category = new Category(existingCategory.getId(), existingCategory.getTitle(), existingCategory.getReports);
I can see the issue once again, which is what the existingCategory object itself contains.
And here my final entities
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "report")
#Data
public class Report {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "report_title", nullable = false)
private String reportTitle;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
#Entity
#Table(name = "category")
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "category_title", nullable = false)
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;
public Category(UUID id, String title) {
this.id = id;
this.title = title;
}
public void addReport(Report r) {
if (this.reports == null) {
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}
public void removeReport(Report r) {
if (this.reports != null) {
r.setCategory(null);
this.reports.remove(r);
}
}
}

Delete records in ManyToMany relationship spring data jpa

I have Two Entities. A and B. Relationship between A and B is #ManyToMany. So I have introduced Third entity C for #ManyToMany relationship as it needed for project.
My Entity classes are look like following.
#Entity
class A
{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "a")
List<C> cList;
}
#Entity
class B
{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "b")
List<C> cList;
}
#Entity
class C
{
#ManyToOne
#JoinColumn(name = "ref_a")
A a;
#ManyToOne
#JoinColumn(name = "ref_b")
B b;
}
Now, I want to delete record of entity A or B then it should delete respective record from C.
But when I delete record of A or B it shows
Cannot delete or update a parent row: a foreign key constraint fails
What other configuration it need to delete record from A or B and it will also delete respective record from C?
You don't have to create an entity to map the Many To Many table. The ManyToMany JPA annotation is there. Here is a sample of how to do it.
#Entity
public class Team {
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, mappedBy="teams")
private List<Match> matches;
}
#Entity
public class Match {
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
#JoinTable(
name="MATCH_TEAM",
joinColumns={#JoinColumn(name="MATCH_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="TEAM_ID", referencedColumnName="ID")})
private List<Team> teams;
}

Resources