Hibernate fetches a deleted child entity from the parent entity - spring-boot

TripEntity
public class TripEntity extends BaseEntity {
.
.
#JsonManagedReference(value = "segment-trip")
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "TRIP_ID")
private List<SegmentEntity> segmentList = new ArrayList<>();
.
.
.
}
SegmentEntity:
public class SegmentEntity extends BaseEntity {
.
.
.
#JsonBackReference(value = "segment-trip")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TRIP_ID")
private TripEntity trip;
.
.
}
With in the same transaction, Deleted one segment that was part of the trip, but when I am fetching the trip using the trip id, getting the deleted segment with other segments as well.
Delete:
#Modifying
#Transactional
#Query(value = "delete from dis_segment where segment_id in :segmentDTOIds", nativeQuery = true)
void deleteSegmentsBySegmentIds(List<Long> segmentDTOIds);
But while fetching the trip :
TripDTO parentTrip = tripServicePort.getTripByIdAndOrgId(parentTripId);
parentTrip shows the deleted segments as well and after updating the trip using save() method, it is giving error : nested exception is javax.persistence.entitynotfoundexception
Please guide about how I can resolve this issue.

Related

How does caching works for many to one mapping in hibernate

I have two entities station and company .
Station has many to one mapping with company. When Iam trying to fetch the list of stations based on company . It always hit the database. Below is my station entity class
#Table(name = "station")
#Setter
#Getter
#Builder
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Station {
public Station() {}
#Id
#SequenceGenerator(name = "mySeqGen", sequenceName = "station_seq",allocationSize = 1)
#GeneratedValue(generator = "mySeqGen")
#Column(name = "station_id ")
private Long id;
Double latitude;
Double longitude;
#ManyToOne(fetch = FetchType.LAZY , cascade = CascadeType.ALL)
#JoinColumn(name = "company_id" , unique =true)
#Fetch(value = FetchMode.JOIN)
Below is my code to find station list based on company
#Override
public List<StationDTO> findStations(final FindStationRequestDTO findStationRequestDTO) {
var company = companyRepository.findById(findStationRequestDTO.getRequestCompanyId());
var stationList = stationRepository.findByCompany(company);
return stationList.stream().map( station -> stationMapper.convertToStationDto(station , new StationDTO())).collect(Collectors.toList());
}
caching is working for companyRepository.findById(findStationRequestDTO.getRequestCompanyId());
public interface StationRepository extends JpaRepository<Station, Long> {
#QueryHints({
#QueryHint(name = HINT_CACHEABLE, value = "true")
})
List<Station> findByCompany(final Optional<Company> company);
}
adding QueryHints made it working

Spring hibernate unnecessary version update queries

I have a spring boot application connecting to mysql db.
I have nested entities in it, all with a version column
public class Entity1 {
.
.
.
#Version
#Column(
name = "version"
)
protected Long version = 0L;
.
.
.
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
List<Entity2> entity2List;
}
public class Entity2 {
.
.
.
#Version
#Column(
name = "version"
)
protected Long version = 0L;
.
.
.
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
List<Entity3> entity3List;
}
This is nested again for Entity3 and Entity4. Entity3 and Entity4 also have a version column.
I am doing this in a method
public void someMethod(...) {
Entity1 entity1 = findOneById(id);
transactionalMethod();
}
#Transactional(rollbackFor = Exception.class)
public void transactionalMethod() {
// Do nothing
return;
}
When the transaction from the transactionalMethod commits, I can see queries incrementing the version column for all Entity4 instances that were loaded as children of entity1. I have not run any update queries on entity1 and any of its children.
I am unable to figure out in which case would this happen?

Springboot - list of objects with child entities not returned

I have this object named SubmittedQuiz, it consists of a Quiz object, User object and submittedQuestions object.
When I try to do this request:
GET http://localhost:8080/SubmittedQuiz/getForUser/10
I get returned the following error:
Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ArrayList[0]->edowl.Model.SubmittedQuiz["user"]->edowl.Model.User$HibernateProxy$lNsgwyQb["hibernateLazyInitializer"])"
The request finds the objects fine, when setting breakpoints it actually gets the list of objects however it fails on the return statement.
The controller method is as shown below:
#GetMapping("/getForUser/{id}")
public ResponseEntity<List<SubmittedQuiz>> getSubmittedQuizForUser(#PathVariable("id") Long id){
List<SubmittedQuiz> quizzes = submittedQuizService.findAllByUserId(id);
return new ResponseEntity<>(quizzes, HttpStatus.OK); //ok is 200 status code
}
The Service is shown below:
public List<SubmittedQuiz> findAllByUserId(Long id) {
return submittedQuizRepo.findAllByUserId(id);
}
The Repo is shown below:
List<SubmittedQuiz> findAllByUserId(Long id);
The SubmittedQuiz is shown below:
#Entity
#Table(name = "Submitted_Quiz")
public class SubmittedQuiz {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "User_Quiz_Submitted",
joinColumns = { #JoinColumn(name = "quiz_submitted_id")},
inverseJoinColumns = { #JoinColumn(name = "user_id")})
public User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "Quiz_Quiz_Submitted",
joinColumns = { #JoinColumn(name = "quiz_submitted_id")},
inverseJoinColumns = { #JoinColumn(name = "quiz_id")})
public Quiz quiz;
private float score;
private LocalDate generatedDate;
private float timeTaken;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "quiz_submitted_question",
joinColumns = { #JoinColumn(name = "quiz_submitted_id")},
inverseJoinColumns = { #JoinColumn(name = "question_id")})
#Column(name = "submitted_questions")
private Set<SubmittedQuestion> submittedQuestions = new HashSet<>();
I saw one suggestion about putting #JsonBackReference & #JsonManagedReference annotations on the objects.
However I haven't needed to do this on any other object thus far and the current annotations I have used sufficed fine till this point
Are there any suggestions?
You could try to use EntityGraph for this purpose.
And set to atributePaths all entities which have FetchType.LAZY:
#EntityGraph(attributePaths = {"user", "quiz", "submitted_questions"})
List<SubmittedQuiz> findAllByUserId(Long id);
Some hint for controller - you don't need to set 200 response directly. Status code OK is returned by default. So following will be fine:
#GetMapping("/getForUser/{id}")
public List<SubmittedQuiz> getSubmittedQuizForUser(#PathVariable("id") Long id){
return submittedQuizService.findAllByUserId(id);
}
UPDATE:
Try to add web configuration like::
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Bean
public Module datatypeHibernateModule() {
return new Hibernate5Module();
}
}
If it wouldn't help to solve the issue with the error try to add:
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
to all your subentities:
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(...)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public User user;
Also, JPA API requires that your entities have to be serializable.
You have to update it like follows:
public class SubmittedQuiz implements Serializable {
private static final long serialVersionUID = 1L;
Add the same for other entities as well (User, Quiz...)

JpaRepository - Inner Join - Subsequent selects

I'm facing a problem when I try to get an list of ServiceCup with its ServiceLanguage. When I try to manipulate the list of ServiceCup in my service layer hibernate is executing a second query and populate my ServiceCup with all ServiceLanguage again.
ServiceCup x ServiceLanguage x LanguageCup
ServiceCup:
#Data
#Entity
#Table(name = "csm_service")
public class ServiceCup extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
private String context;
// bi-directional many-to-one association to CsmServiceLanguage
#OneToMany(mappedBy = "service")
private List<ServiceLanguage> serviceLanguages;
}
ServiceLanguage:
#Data
#Entity
#Table(name = "csm_service_language")
public class ServiceLanguage extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "translated_name")
private String translatedName;
// bi-directional many-to-one association to CsmLanguage
#ManyToOne
#JoinColumn(name = "csm_language_id_fk")
private LanguageCup language;
// bi-directional many-to-one association to CsmService
#ManyToOne
#JoinColumn(name = "csm_service_id_fk")
private ServiceCup service;
}
JpaRepository:
#Query(value = "select s, sl from ServiceCup s \n" + "INNER JOIN FETCH ServiceLanguage sl on s.id = sl.service \n"
+ "where sl.language.id = :languageId")
List<ServiceCup> findAllServicesByLanguageId(#Param("languageId") String languageId);
Query in repository layer:
select *all_fields* from csm_service servicecup0_ inner join csm_service_language servicelan1_ on (servicecup0_.id=servicelan1_.csm_service_id_fk) where servicelan1_.csm_language_id_fk=?
But in service layer execute a lot of queries to bring all the relations of ServiceCup. I want the ServiceCup objects populate but only with the results that are in the query.
How can I get a ServiceCup object with only the results of the query?
PS: In my method in service layer I have #Transactional(readOnly = true) but if I remove I can't get the objects related to ServiceCup.
I needed to use projection and not the entity to execute the query only once.
ServiceCupProj
public interface ServiceCupProj {
public Long getId();
public String getDescription();
public String getInternalname();
......
Repository
#Repository
public interface CupServiceRepository extends JpaRepository<ServiceCup, Long> {
#Query(value = "select servicecup0_.id as id, servicelan1_.translated_description as description, servicelan1_.translated_name as internalname \n"
+ "from csm_service servicecup0_ inner join csm_service_language servicelan1_ on (servicecup0_.id=servicelan1_.csm_service_id_fk) \n"
+ "where servicelan1_.csm_language_id_fk = :languageId ", nativeQuery = true)
List<ServiceCupProj> findAllServicesByLanguageId(#Param("languageId") String languageId);
......
And in the service layer I transform this ServiceCupProj in the entity that I need.

How to access the fields of an embedded class in Spring JPA

I have a class with 2 fields marked with #Id
#Entity
#Table(name="baspas")
class BasPas
#Id
#ManyToOne
#JoinColumn(name="bas_id", referenceColumnName="id")
private Bas basboard;
#Id
#ManyToOne
#JoinColumn(name = "pas_id", referenceColumnName = "id")
private pas pasboard;
//
I refactored them to an Embedded class and pulled the above two #Id fields in the BasPasPK class. This will enable me to create an interface which will extend the JPARepository interface.
#Embeddable
class BasPasPK {
#ManyToOne
#JoinColumn(name="bas_id", referenceColumnName="id")
private Bas basboard;
#ManyToOne
#JoinColumn(name = "pas_id", referenceColumnName = "id")
private pas pasboard;
//
}
As both these fields are annotated #ManyToOne there is another end of the relationship, where in these fields are listed with "mappedBy".
for eg.
#Entity
class Another{
.
.
#OneToMany(mappedBy = "basboard" cascade = CascadeType.ALL)
private set<BasPas> basPas;
.
.
.
}
But after refactoring how to access the other end of the class.
What I mean is when I am doing mvn spring-boot:run I am getting the following exception
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property
then what I did was to change the class name in
#Entity
class Another{
.
.
#OneToMany(mappedBy = "basboard" cascade = CascadeType.ALL)
private Set<BasPas> basPas;
.
.
.
}
to this
class Another{
.
.
#OneToMany(mappedBy = "bas" cascade = CascadeType.ALL)
private set<BasPasPk> basPas; //changed the classname in angle brackets to BasPasPk
.
.
.
}
But after this I started getting this following exception.
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class.
How to fix this, I mean how to access these properties in the other class after pulling those two property in the embedded class.
try it this way: (Assuming the name of the BasPasPK property in your entity is id)
#Entity
class Bas{
.
.
#OneToMany(mappedBy = "id.basboard" cascade = CascadeType.ALL)
private Set<BasPas> basPas;
.
.
.
}
Have you tried annotating the class BasPas with #Entity ?

Resources