JPA OneToMany delete on parent don't remove linked entities - java

I'm using SpringBoot and JPA + Hibernate.
I have these entities:
#Entity
#Table(name = "post")
#AllArgsConstructor #NoArgsConstructor
#Getter #Setter
public class Item extends BaseEntity {
#Id
#Column(name = "id")
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
#OneToMany(mappedBy = "post", orphanRemoval=true, fetch = FetchType.EAGER)
#MapKey(name="userId")
private Map<UUID, Share> shares;
....
#CreationTimestamp
#Column(name = "creation_date")
#Setter(AccessLevel.NONE)
private Instant creationDate;
#UpdateTimestamp
#Column(name = "last_mod_date")
#Setter(AccessLevel.NONE)
private Instant lastModificationDate;
}
#Entity
#Table(name = "comment"
#AllArgsConstructor #NoArgsConstructor
#Getter #Setter
public class Comment extends BaseEntity {
#Id
#Column(name = "id")
#Type(type = "uuid-char")
#Setter(AccessLevel.NONE)
private UUID uuid = UUID.randomUUID();
#ManyToOne(fetch = FetchType.LAZY)
#NotNull()
#JoinColumn(name = "post_id")
private Post post;
.....
#CreationTimestamp
#Column(name = "creation_date")
#Setter(AccessLevel.NONE)
private Instant creationDate;
#UpdateTimestamp
#Column(name = "last_mod_date")
#Setter(AccessLevel.NONE)
private Instant lastModificationDate;
}
#Test
#Transactional
public void testDeleteItem() {
itemRepo.delete(item);
itemRepo.flush();
}
When I try to delete a parent entity (Post), all related entities Comments remains on the database.
Why doesn't works the cascade deletion?

OrphanRemoval doesn't work. Use cascade = CascadeType.ALL instead

Related

How to simulate lombok #data stackoverflow error within unit tests for repository class?

I have a relationship between entities that throws a stack overflow error if the #Data annotation from Lombok is used instead of the individual #Getter and #Setter annotations. This is fixed now, but I would like to write a unit test for it within my repository tests. However, I'm not sure how to achieve that and haven't been able to find samples for it.
Here are my entity classes:
#Entity
#Table(name = "users")
#Builder
//#Getter
//#Setter
#Data
#AllArgsConstructor
#NoArgsConstructor
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
#ManyToMany
#JoinTable(
name = "users_hobbies",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "hobby_and_interest_id", referencedColumnName = "id"))
private Set<HobbyAndInterestEntity> hobbyAndInterestEntities;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "hometown_id", referencedColumnName = "id")
private HometownEntity hometownEntity;
#Entity
#Table(name = "hometown")
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
public class HometownEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "city")
private String city;
#Column(name = "country")
private String country;
#OneToMany(mappedBy = "hometownEntity", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = false)
private Set<UserEntity> userEntitySet;
#Data
#AllArgsConstructor
#NoArgsConstructor
public class HobbyAndInterestEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "title")
private String title;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "hobbyAndInterestEntities")
private Set<UserEntity> userEntities;
And here is my test for a case without the exception, which I was aiming to modify to test for the exception scenario:
#Test
void testGetUser() {
UserEntity userEntity = saveUserEntity();
assertTrue(userRepository.findAll().size() > 0);
userEntity = userRepository.findById(userEntity.getId()).orElse(null);
assertNotNull(userEntity);
UserEntity finalUserEntity = userEntity;
assertAll(
() -> assertEquals("anyName", finalUserEntity.getName()),
() -> assertEquals("anyCountry", finalUserEntity.getHometownEntity().getCountry()),
() -> assertTrue(finalUserEntity.getHobbyAndInterestEntities().size() > 0));
finalUserEntity.getHobbyAndInterestEntities().forEach(h -> assertEquals("anyInterest", h.getTitle()));
}
#NotNull
private UserEntity saveUserEntity() {
HometownEntity hometownEntity = HometownEntity.builder().city("anyCity").country("anyCountry").build();
hometownEntity = hometownRepository.save(hometownEntity);
HobbyAndInterestEntity hobbyAndInterestEntity = HobbyAndInterestEntity.builder()
.title("anyInterest")
.build();
hobbyAndInterestEntity = hobbyAndInterestRepository.save(hobbyAndInterestEntity);
Set<HobbyAndInterestEntity> hobbyAndInterestEntities = new HashSet<>();
hobbyAndInterestEntities.add(hobbyAndInterestEntity);
UserEntity userEntity = UserEntity.builder()
.name("anyName")
.hometownEntity(hometownEntity)
.hobbyAndInterestEntities(hobbyAndInterestEntities)
.build();
return userRepository.save(userEntity);
}
So in summary, I know the application is throwing the stack overflow when I have the #Data annotation and so I would like to write a test that would fail for it and pass again when I modify the entity class to use #Getter and #Setter, but not sure what is needed here and would appreciate some guidance, please.
Thank you very much.
Could you check #Data annotation here. #Data is a shortcut for #ToString, #EqualsAndHashCode, #Getter on all fields, #Setter on all non-final fields, and #RequiredArgsConstructor! When you call toString or equals or hashCode method, the relationship entities will query in the database. You can try to review generated source, the relationship entities is used in those methods. I think it can throw a stack overflow error.

Is it possible to implement saving changes to child objects in hibernate?

I have such entities:
#Entity
#Getter
#Setter
#ToString(of = {"id", "type", "hostId"})
#EqualsAndHashCode(of = {"id", "hostId", "player"})
#NoArgsConstructor
#TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public class ContentPlayer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Content_id")
private Content content;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "player_id")
#JsonView(Views.FullContent.class)
private Player player;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "translation_id")
#JsonView(Views.FullContent.class)
private Translation translation;
#Length(max = 155)
#NotNull
#JsonView(Views.FullContent.class)
private String hostId;
#Enumerated(EnumType.STRING)
#org.hibernate.annotations.Type(type = "pgsql_enum")
#JsonView(Views.FullContent.class)
private TranslationType translationType;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedByPlayer;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdByPlayer;
#CreationTimestamp
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime created;
#OneToOne(mappedBy = "contentPlayer", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
#JsonView(Views.FullContent.class)
private Series series;
}
#Entity
#Getter
#Setter
#ToString(of = {"id", "seasons"})
#EqualsAndHashCode(of = {"id", "episodesTotal", "seasons"})
#NoArgsConstructor
public class Series {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JsonView(Views.FullContent.class)
private int lastSeason;
#JsonView(Views.FullContent.class)
private int lastEpisode;
#JsonView(Views.FullContent.class)
private int episodesCount;
#JsonView(Views.FullContent.class)
private int episodesTotal;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#JsonView(Views.FullContent.class)
private LocalDate nextEpisode;
#OneToMany(mappedBy = "series", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonView(Views.FullContent.class)
private Set<Season> seasons = new LinkedHashSet<>();
#OneToOne
#MapsId
#JoinColumn(name = "id")
private ContentPlayer contentPlayer;
}
#Entity
#Getter
#Setter
#ToString(of = {"id", "number"})
#EqualsAndHashCode(of = {"id", "number"})
#NoArgsConstructor
public class Season {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
#JsonView(Views.FullContent.class)
private int number;
#OneToMany(mappedBy = "season", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonView(Views.FullContent.class)
private Set<Episode> episodes = new LinkedHashSet<>();;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "series_id", nullable = false, updatable = false)
private Series series;
}
When I try to save the ContentPlayer with all child entities - everything goes fine, but if I want to add / change some data in the child entity - this leads to an error, although I indicated the cascade.
Error Example:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.core.entities.content.Season.series -> com.core.entities.content.Series;
Is it possible to implement saving changes to child objects? Adding each child object individually is not really appropriate.

Problems with Hibernate Mapping

I'm having problem with mapping two classes with composite keys.
The first class is Product:
#Entity
#Table(name = "Products")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#SuperBuilder
public class Product {
#EmbeddedId
private ProductKey prodPK;
#Column(name = "name", length = 50, nullable = false)
private String name;
#Column(name = "description", length = 80)
private String description;
#Column(name = "totalStock", columnDefinition = "double(8,2) default 0")
private double totalStock;
#ManyToOne
#JoinColumn(name = "companyId", referencedColumnName = "id", nullable = false)
private Company company;
}
With this #EmbeddedId:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Embeddable
public class ProductKey implements Serializable {
#Column(name = "sku", length = 50)
private String sku;
#Embedded
private LotKey lot;
}
At the same time, this embedded class has as part of its composite key another composite key "LotKey"
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Embeddable
public class LotKey implements Serializable {
#Column(name = "lot")
private String lot;
#ManyToOne
#JoinColumn(name = "company", referencedColumnName = "id")
private Company company;
}
which belongs to the class:
#Entity
#Table(name = "Lots")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#SuperBuilder
public class Lote {
#EmbeddedId
private LotKey lotpk;
#Column(name = "stock")
private double stock;
#Column(name = "expirationDate", columnDefinition = "default current_timestamp()")
private Date expirationDate;
}
But I'm having trouble referencing to them:
#Entity
#Table(name = "quantityProduct")
public class QuantityProduct{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#ManyToOne
#JoinColumns({
#JoinColumn(
name = "sku",
referencedColumnName = "sku"),
#JoinColumn(
name = "lot")
})
private Product product;
#Column(name = "quantity", columnDefinition = "double(8,2) default 0")
private double quantity;
}
I am getting the following error
image
Thank you so much !
In QuantityProduct, set also referencedColumnName in
#JoinColumn(
name = "lot")

how to save bidrectional entity with dto

I have two spring boot entities MeetingSetting and MeetingTime, MeetingSetting can have multiple MeetingTimes. I am trying to save these to at the same time with the DTO structure, so I can avoid the circular reference problem when I am getting MeetingTimes. Saving partially works. MeetingSettings has a property called meetingName which is a foreign key in meetingTimes. Everything except meetingName is saved which is for some reason null, but I can not find the reason, could someone maybe look at my code and tell me what I am missing?
MeetingSetting Entity:
#Entity
#Table(name = "meeting_settings")
#Setter
#Getter
public class MeetingsSetting implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "meeting_name", unique = true)
private String meetingName;
#Column(name = "meeting_url")
private String meetingUrl;
#Column(name = "meeting_pw")
private String meetingPw;
#OneToMany(mappedBy = "meetingName", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<MeetingTime> meetingTime = new HashSet<>();
}
MeetingSettingDTO:
#Getter
#Setter
public class MeetingSettingDTO {
private Long id;
#NotNull
private String meetingName;
#NotNull
private String meetingUrl;
#NotNull
private String meetingPw;
private Set<MeetingTime> meetingTime;
}
MeetingTime Entity:
#Entity
#Table(name = "meeting_times")
#Getter
#Setter
public class MeetingTime implements Serializable {
#JsonIgnore
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "meeting_date")
private String date;
#Column(name = "start_time")
private String startTime;
#Column(name = "end_time")
private String endTime;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "meeting_name" , referencedColumnName = "meeting_name")
private MeetingsSetting meetingName;
}
MeetingTimeDTO:
#Getter
#Setter
public class MeetingTimeDTO {
private Long id;
#NotNull
private String date;
#NotNull
private String startTime;
#NotNull
private String endTime;
private Set<MeetingSettingDTO> meetingSettings;
}
And finally the controller where I am saving everything (Just save method):
#PostMapping("/")
public void saveMeeting(#RequestBody MeetingSettingDTO meetingSettingDTO){
MeetingsSetting meetingsSetting = new MeetingsSetting();
meetingsSetting.setMeetingName(meetingSettingDTO.getMeetingName());
meetingsSetting.setMeetingPw(meetingSettingDTO.getMeetingPw());
meetingsSetting.setMeetingUrl(meetingSettingDTO.getMeetingUrl());
Set<MeetingTime> meetingTimeSet = meetingSettingDTO.getMeetingTime();
meetingsSetting.setMeetingTime(meetingTimeSet);
meetingSettingService.saveMeeting(meetingsSetting);
}
My service is just implementing a jpaRepository which takes MeetingSetting as parameter
In your MeetingTime entity class you have a parent:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "meeting_name" , referencedColumnName = "meeting_name")
private MeetingsSetting meetingName;
You have to set it explicitly for each MeetingTime, so add this:
Set<MeetingTime> meetingTimeSet = meetingSettingDTO.getMeetingTime();
meetingTimeSet.forEach(m -> m.meetingName(meetingsSetting));

Hibernate - Entity audit

I have one entity and I want to track all changes therefore I created new Entity for audit.
Below is my primary entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "primary")
public class PrimaryEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "primary_id")
private Long id;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#ElementCollection
#CollectionTable(
name = "primary_attachments",
joinColumns = #JoinColumn(name = "primary_id")
)
private List<String> attachments;
#CreatedDate
#Temporal(TemporalType.DATE)
private Date createDate;
#LastModifiedDate
#Temporal(TemporalType.DATE)
private Date lastModifiedDate;
}
And below is my entity for audit:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "primary_audit")
public class PrimaryEntityAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "audit_id")
private Long id;
#NotNull
#Column(name = "primary_entity_id")
private Long primaryId;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#ElementCollection
#CollectionTable(
name = "primary_attachments_audit",
joinColumns = #JoinColumn(name = "primary_entity_id")
)
private List<String> attachments = new ArrayList<>();
#CreatedDate
#Temporal(TemporalType.DATE)
private Date createDate;
public PrimaryEntityAudit(PrimaryEntity primaryEntity) {
this.primaryId = primaryEntity.getId();
this.attachments.addAll(primaryEntity.getAttachments());
this.createDate = new Date();
}
}
And before update primary entity I create new PrimaryEntityAudit and save this object and then update the primary entity.
And operation is successful and object PrimaryEntityAudit is saved, but attachments from PrimaryEntityAudit aren't saved.
I tried also in constructor of ProjectEntityAudit do setAttachments, but then I got an Exception: HibernateExcpetion: Found shared references to collection.
How should I map the collections of audit for saving old state of PrimaryEntity attachments?
You should look at the following hibernate module Envers
It provides features for versioning and auditing
It is preferable to not reinvent the wheel except if you have technicals constraints which prevent you to use some frameworks or others.

Categories

Resources