Springboot - list of objects with child entities not returned - spring-boot

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...)

Related

Automatic JPA refresh ManyToOne objects with #Version feature

I'm getting an exception:
org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance
- save the transient instance before flushing :
com.example.jpamapstruct.entity.Member.club ->
com.example.jpamapstruct.entity.Club
while saving the member entity:
#Transactional
public MemberDto save(MemberDto memberDto){
Member entity = memberMapper.toEntity(memberDto);
return memberMapper.toDto(repository.save(entity));
}
How to fix this case in a proper way?
Possible solution:
I can get and set a club object before saving a member but is it only one and the best approach in such scenario?
Member entity = memberMapper.toEntity(memberDto);
clubRepository.getReferencedById(memberDto.getClubId()).ifPresent(entity::setClub);
return memberMapper.toDto(repository.save(entity));
Questions:
Should I put this getReferencedById code explicity? I mean what if we have several child objects (unidirectional ManyToOne), for each we need to get data from DB.
Is there any way to handle this by JPA (Spring Data/JPA) "automatically"?
Maybe it is possible to hit DB only one time with f.e join fetch somehow for all childs (with using custom #Query or querydsl or criteria/specification)?
Next, hoow to handle collections (unidirectional manyToMany)? In my case set of events in member object. Also need to loop thru and get all objects one by one before saving member?
Where should I put such logic in a service or maybe better in a mapstuct mapper?
If so, how to use repositories in such mapper?
#Mapper(componentModel = "spring")
public interface MemberMapper extends EntityMapper<MemberDto, Member> {
#AfterMapping
default void afterMemberMapping(#MappingTarget Member m, MemberDto dto) {
var club = clubRepo.findById(m.getClub().getId())
m.setClub(club)
}
Source code:
#Entity
public class Club extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
}
public class ClubDto extends AbstractDto {
private Long id;
}
#Entity
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
// commented out as don't want to save child object as it should already exist
// #ManyToOne(cascade = CascadeType.ALL)
#ManyToOne
Club club;
#ManyToMany
#JoinTable(name = "member_events",
joinColumns = #JoinColumn(name = "member_id"),
inverseJoinColumns = #JoinColumn(name = "event_id")
)
List<Event> events = new ArrayList<>();
}
public class MemberDto {
private Long id;
private ClubDto club;
}
#MappedSuperclass
public abstract class AbstractEntity {
#Version
private Integer version;
}
public abstract class AbstractDto {
private Integer version;
}
//MemberMapper above

Spring Data Projection with OneToMany error

I have a entity call Circuit.
#Entity
public class Circuit implements Comparable<Circuit>, Serializable {
#Column
private String id;
#OneToMany(mappedBy = "circuit", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<Step> workflow = new HashSet<>();
...
}
I have a class called CircuitLight
public class CircuitLight {
private String id;
private Set<Step> workflow;
/* constructor, getters and setters */
}
In my CircuitRepository, i'm trying to make a projection
#Transactional(readOnly = true)
#Query("select new com.docapost.circuit.CircuitLight(c.id, c.workflow) from Circuit c where c.account.siren = :siren")
Set<CircuitLight> findAllByAccountSirenProjection(#Param("siren") String siren);
When i execute, i have a error message:
could not extract ResultSet; SQL [n/a] com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'circuit0_.id' in 'on clause'
I try with other entity. Every time i have a property with a relation #OneToMany, i have the issue...
Is it possible to make a projection with class (Without use a interface) when there are a relation OneToMany ?
UPDATE:
Step.class
#Entity
public class Step implements Comparable<Step>, Serializable {
private static final List<String> INDEXABLE_PROCESSES = Arrays.asList(
ParapheurWorkflowModel.SERVER,
ParapheurWorkflowModel.SIGN,
ParapheurWorkflowModel.VISA
);
#Id
#GeneratedValue
#Expose
#SerializedName("step_id")
public long id;
#ManyToOne
public Circuit circuit;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(joinColumns = #JoinColumn(name = "step_id"), inverseJoinColumns = #JoinColumn(name = "technicalGroup_id"))
private List<TechnicalGroup> technicalGroups = new ArrayList<>();
#Column(name = "step_type", nullable = false)
#Expose
#SerializedName("subprocess_ref")
public String type;
#Column(nullable = false)
public int orderIndex;
/* contructor, getters and setters */
}
UPDATE 2:
Hum.... My bad, in my circuit class, i have a EmbeddedId
#EmbeddedId
private CircuitPK key;
#Embeddable
public static class CircuitPK implements Serializable {
public String id;
public String siren;
}
I try with this code in Step.class
#ManyToOne
#JoinColumns(value = {
#JoinColumn(name = "circuit_siren", referencedColumnName = "siren"),
#JoinColumn(name = "circuit_id", referencedColumnName = "id")
})
public Circuit circuit;
The result is the same
Write the following code in the Step entity
#ManyToOne
#JoinColumn(name="id", nullable=false)
private Circuit circuit;

JPA throws lazy loading exception even it is eager fetch

User entity
#Entity
#Table(name = "T_USER")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
//A user can be employee in many organizations so he look at the same time as many emplyee
#JsonManagedReference(value = "user-association")
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private Set<Association> associations;
....
Association entity
#Entity
#Table(name = "T_ASSOCIATION")
public class Association implements Serializable {
#EmbeddedId
private AssociationId associationId;
private String permission;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference(value = "user-association")
#JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false)
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference(value = "organization-association")
#JoinColumn(name = "organization_id", referencedColumnName = "id", insertable = false, updatable = false)
private Organization organization;
POST endpoint
#PostMapping(path = "/{id}/users", consumes = MediaType.APPLICATION_JSON_VALUE)
public List<User> associateUserToOrganization(#RequestBody AssociationDTO userToOrgDTO, #PathVariable String id) {
Association association = new Association(new AssociationId(userToOrgDTO.getUserId(), userToOrgDTO.getOrganizationId()));
association.setPermission("GUEST_SET");
User userToAffect = userRepository.findById(userToOrgDTO.getUserId()).get();
Organization orgToAffect = organizationRepository.findById(userToOrgDTO.getOrganizationId()).get();
userToAffect.addAssociation(association);
orgToAffect.addAssociation(association);
organizationRepository.save(orgToAffect);
return userRepository.findAll().stream().filter(user -> !user.getAssociations().isEmpty()).collect(Collectors.toList());
}
When I add a user to the organization using POSTMAN with correct input, the famous error of serialization of lazy loading appears even I am not using fetch type Lazy
this is the error
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]->jpa.workspace.manytomanyadditional.domain.User$HibernateProxy$pL9wPAuw["hibernateLazyInitializer"])
So i made the solution as it is mentioned in this exception and i put in application.properties,
spring.jackson.serialization.fail-on-empty-beans=false
and #JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) to remove it from the json
The issue is fixed sure, but why I am supposed to consider the User->associations relationship as Lazy fetching and not eager as I chose it...Is it related to #JsonManagedReference maybe?
am using spring 2.4.1 and hibernate (core 5.4.25 & annotations 5.1.2 Final)
Remove #JsonManagedReferenced, #JsonBackReference and add these following in your config class
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jackson2HttpMessageConverter());
}
#Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(jacksonBuilder().build());
return converter;
}
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
Hibernate5Module hibernateModule = new Hibernate5Module();
hibernateModule.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, false);
builder.modules(hibernateModule);
builder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
builder.featuresToDisable(MapperFeature.DEFAULT_VIEW_INCLUSION);
return builder;
}

spring data jpa: No aliases found in result tuple! Make sure your query defines aliases

When I try to get the users using repository interface I received following exception "org.springframework.dao.InvalidDataAccessApiUsageException: No aliases found in result tuple! Make sure your query defines aliases!; nested exception is java.lang.IllegalStateException: No aliases found in result tuple! Make sure your query defines aliases!"
Repository:
#Repository
public interface UserRelationshipRepository
extends JpaRepository<UserRelationship, Long>, QueryDslPredicateExecutor<UserRelationship> {
#Query(value = "SELECT ur.id.toUser FROM UserRelationship ur WHERE ur.fromUser = :fromUser AND ur.relationshipTypeId = 1")
Set<User> findUserFriends(#Param("fromUser") User fromUser);
}
Entities:
#Entity
#NamedEntityGraph(name = "graph.User", attributeNodes = {})
#Table(name = "users")
public class User extends BaseEntity implements UserDetails {
private static final long serialVersionUID = 8884184875433252086L;
#Id
#SequenceGenerator(name = "users_id_seq", sequenceName = "users_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "users_id_seq")
private Long id;
#Column(name = "first_name")
private String firstName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "fromUser", cascade = CascadeType.ALL)
private Set<UserRelationship> relationships = new HashSet<UserRelationship>();
// getters setters
}
#Entity
#NamedEntityGraph(name = "graph.UserRelationship", attributeNodes = {})
#Table(name = "users_relationships")
public class UserRelationship extends BaseEntity implements Serializable {
private static final long serialVersionUID = -6367981399229734837L;
#EmbeddedId
private final UserRelationshipId id = new UserRelationshipId();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "from_user_id", nullable = false)
#MapsId("fromUserId") // maps fromUserId attribute of the embedded id
private User fromUser;
#Column(name = "relationship_type_id")
private Long relationshipTypeId;
}
I am using '1.11.0.BUILD-SNAPSHOT' version of spring data jpa.
This is already known issue, and it is marked as resolved, but I am still get it.
Please, help me to solve this.
Update:
If I change repository method's return type to Set<Object> then all works fine.
You're running into DATAJPA-885, which is already fixed and will be part of the Spring Data Hopper SR2 release.

adding excerpt projection to spring data rest repository causes stack overflow error

We are facing StackoverflowError when we enable an except projection on a REST repository. The entity is question has two associations, a #ManyToOne with one Venue entity that has to be included inline for all responses, and a #OneToMany with Trainee entity we always want to hide. The entity (relevant) snippet
#Entity
#EntityListeners(AuditingEntityListener.class)
public class Workshop implements Serializable {
private static final long serialVersionUID = -5516160437873476233L;
private Long id;
// omitted properties
private Venue venue;
private Set<Trainee> trainees;
#Id
#GeneratedValue
public Long getId() {
return id;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinTable(name = "workshop_venue", joinColumns = { #JoinColumn(name = "workshop_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "venue_id", referencedColumnName = "id") })
public Venue getVenue() {
return venue;
}
#OneToMany
#JoinTable(name = "workshop_trainees", joinColumns = { #JoinColumn(name = "workshiop_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "trainee_id", referencedColumnName = "email") })
#RestResource(exported = false)
#JsonIgnore
public Set<Trainee> getTrainees() {
return trainees;
}
// omitted getters/setters
}
when I add this Projection
#Projection(name = "default", types = { Workshop.class })
public interface InlineVenueProjection {
String getName();
Integer getSeatsAvailable();
WorkshopType getWorkshopType();
Date getDate();
Venue getVenue();
}
and enable it in the repository
#RepositoryRestResource(collectionResourceRel = "workshop", path = "workshops", excerptProjection = InlineVenueProjection.class)
public interface WorkshopRepository extends JpaRepository<Workshop, Long> {
// omitted methods
}
I get a stack overflow error in POST requests
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.StackOverflowError
and further in the stack trace
Caused by: java.lang.StackOverflowError: null
at java.util.concurrent.locks.ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter.initialValue(ReentrantReadWriteLock.java:286)
at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:180)
at java.lang.ThreadLocal.get(ThreadLocal.java:170)
at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryReleaseShared(ReentrantReadWriteLock.java:423)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared(AbstractQueuedSynchronizer.java:1341)
at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.unlock(ReentrantReadWriteLock.java:881)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:169)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:140)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:67)
at org.springframework.data.mapping.context.PersistentEntities.getPersistentEntity(PersistentEntities.java:63)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:100)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:352)
The reason you are getting Stack overflow exception is because I guess there is a bidirectional relationship defined between Venue (1-Many) and Workshop (Many-1). You can confirm my assumption.
When you try to serialize venue directly because of the relationship workshop is loaded and it has a reference to venue and hence the endless recursion.
Solution
To resolve this problem, Jackson has #JsonManagedReference and #JsonBackReference.
Workshop Class
public class Workshop implements Serializable {
...
#ManyToOne(fetch = FetchType.EAGER)
#JoinTable(name = "workshop_venue", joinColumns = { #JoinColumn(name = "workshop_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "venue_id", referencedColumnName = "id") })
#JsonManagedReference
public Venue getVenue() {
return venue;
}
...
}
Venue Class
public class Venue implements Serializable {
...
#OneToMany
#JsonBackReference
public List<Workshop> getWorkshops() {
return workshops;
}
...
}
Hope this helps. Let me know if this works.

Resources