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

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.

Related

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

Hibernate deletion referential integrity constraint violation on many to many association

I am trying to use Hibernate to remove an entity however I get an error: Cannot delete or update a parent row: a foreign key constraint fails
The setup is that I have an abstract class A and two classes (B and C) which extend A. B contains a list of C's (unidirectional relationship). And there is a function to delete A by its ID.
Note: Stuff has been removed for brevity.
#Entity
public class B extends A {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
joinColumns = #JoinColumn(name = "B_A_id"),
inverseJoinColumns = #JoinColumn(name = "C_A_id"))
List<C> cList;
}
#Entity
public class C extends A {
(no reference to B)
}
The issue is that when the deleteAByFixedId is called where A is a C, it tries to delete the C before it deletes the B which references it and therefore I get a foreign key constraint failure.
What am I doing wrong?
The answer will still be updated.
Links:
The best way to use the #ManyToMany annotation with JPA and Hibernate
Hibernate Inheritance Mapping
#ManyToMany
Unidirectional example:
User.java
#Entity
public class User {
#Id
#GeneratedValue
#Column(name = "user_id")
private long id;
...
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public void addRoles(Role role) {
roles.add(role);
}
public void removeRoles(Role role) {
roles.remove(role);
}
}
Role.java
#Entity
public class Role {
#Id
#GeneratedValue
#Column(name = "role_id")
private int id;
#Column(name = "role")
private String role;
}
Bidirectional example:
Trader.java:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#ToString(exclude = "stockmarkets")
#Table(name = "trader")
public class Trader {
#Id
#GeneratedValue
#Column(name = "trader_id")
private Long id;
#Column(name = "trader_name")
private String traderName;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "TRADER_STOCKMARKET",
joinColumns = { #JoinColumn(name = "trader_id") },
inverseJoinColumns = { #JoinColumn(name = "stockmarket_id") })
private Set<Stockmarket> stockmarkets = new HashSet<>();
/*
We need to add methods below to make everything work correctly
*/
public void addStockmarket(Stockmarket stockmarket) {
stockmarkets.add(stockmarket);
stockmarket.getTraders().add(this);
}
public void removeStockmarket(Stockmarket stockmarket) {
stockmarkets.remove(stockmarket);
stockmarket.getTraders().remove(this);
}
}
Stockmarket.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#ToString(exclude = "traders")
#Table(name = "stockmarket")
public class Stockmarket{
#Id
#GeneratedValue
#Column(name = "stockmarket_id")
private Long id;
#Column(name = "stockmarket_name")
private String stockmarketName;
#ManyToMany(mappedBy="stockmarkets")
private Set<Trader> traders = new HashSet<>();
/*
We need to add methods below to make everything work correctly
*/
public void addTrader(Trader trader) {
traders.add(trader);
trader.getStockmarkets().add(this);
}
public void removeTrader(Trader trader) {
traders.remove(trader);
trader.getStockmarkets().remove(this);
}
}

access many to many relation in spring

I have a class called Tag:
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "tags")
private Set<Post> posts = new HashSet<>();
...
}
And a class called Post
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "post_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tag> tags = new HashSet<>();
...
}
It creates another table named post_tags.
How can I write a Controller to access that table as it is not similar a repository?
Is there more easy and convenient way to implement ManyToMany relationship ?
My pom.xml
You don't need to access that relation table manually. You can load load all Tag entities, and then load all the referenced Post entities.
The relation table is enterily managed by your ORM frameork.
But, if you still want to access the relation table, you can use native queries in your Spring Data JPA repository, e.g.
#Query(value="select post_id, tag_id from post_tags", nativeQuery=true)
List<PostTag> loadPostTags();
PostTag class is not a jpa-managed entity and must match the structue of the returned table:
public class PostTag {
private long postId;
private long tagId;
// getter, setter
}
Use this way
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "post_id") })
private Set<Post> posts = new HashSet<>();
...
}
#Entity
#Table(name = "posts")
public class Post {
#Id
#Column(name = "post_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long postId;
...
}

Spring boot domain class required for mapping table

I m new to Spring Boot. I have a table (Team) that has resources, am storing in a separate table (Resources) and have team_resource mapping table (with fields teamid, resourceid). My question is should I have a domain class for the mapping_table too ?
When I m inserting a new team (POST) with resources I create entry in all 3 tables. I m using facade/dao pattern for writing/ reading to the DB. I have to handle when the team is modified/ deleted. Should I have a domain class for the mapping_table?
There are multiple approaches you can handle it
Approach 1
Define #ManyToMany between your Team and Resources entity like this:
In Team Entity
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "resources",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "id") })
private Set<Resources> resources= new HashSet<>();
In your resources entity:
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "resources")
private Set<Team> teams= new HashSet<>();
Approach 2
#Entity
#Table(name = "team_resources")
public class TeamResources implements Serializable {
#EmbeddedId
private TeamResourcesId id;
#ManyToOne
#JoinColumn(name = "team_id", referencedColumnName = "id", insertable = false, updatable = false)
private Team team;
#ManyToOne
#JoinColumn(name = "resources_id", referencedColumnName = "id", insertable = false, updatable = false)
private Resources resources;
public TeamResources (Team u, Resources r) {
// create primary key
this.id = new TeamResourcesId (u.getUserId(), q.getQuestionId());
// initialize attributes
this.user = u;
this.question = q;
}
#Embeddable
public static class TeamResourcesId implements Serializable {
#Column(name = "team_id")
protected Long teamId;
#Column(name = "resources_id")
protected Long resourcesId;
public TeamResourcesId () {
}
public TeamResourcesId (Long teamId, Long resourcesId) {
this.teamId= teamId;
this.resourcesId= resourcesId;
}
//Getter , setter. equal and hash
}
so to answer your question, follow second approach and its good to not define bidirectional approach as it can lead to some run time problem if not handled properly.

object references an unsaved transient instance - Spring, JPA Hibernate

Here is the code:
#Entity
public class PortalUser {
#NotNull
#OneToMany(mappedBy = "portalUser", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<PortalUserOrganisation> portalUserOrganisations;
#NotNull
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "portalUser", orphanRemoval = true)
private Set<UserRole> userRoles = new HashSet<UserRole>();
}
#Entity
public class PortalUserOrganisation {
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private PortalUser portalUser;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ORGANISATION_ID", referencedColumnName = "ID")
private Organisation organisation;
}
#Entity
public class Organisation {
#OneToMany(mappedBy = "organisation", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Set<PortalUserOrganisation> portalUserOrganisations;
}
#Entity
public class UserRole {
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private PortalUser portalUser;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional=true)
#JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")
private RoleLookup roleLookup;
}
#Entity
public class RoleLookup extends AbstractLookupEntity {
#OneToMany(mappedBy = "roleLookup", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Set<UserRole> userRoles = new HashSet<UserRole>();
}
Code to Create a User:
#Transactional
saveUser(userObj)
PortalUser portalUser = new PortalUser;
portalUser.setStatus(status);
PortalUserOrganisation userOrganisation = null;
for (OrganisationsDto dto : organisationsList()) {
userOrganisation = new PortalUserOrganisation();
userOrganisation.setOrganisation(organisationRepository.findOne(dto.getId()));
userOrganisation.setPortalUser(portalUser);
userOrganisation.setCreatedUpdatedBy(context.getName());
userOrganisation.setCreatedUpdatedDate(createUpdateDate);
userOrganisation.setAction(portalUser.getAction());
userOrganisation.setStatus(portalUser.getStatus());
userOrganisation.setActive(true);
portalUser.getPortalUserOrganisation().add(userOrganisation);
}
UserRole userRole = null;
for (RoleLookupDto dto : portalUserDto.getUserRoles()) {
userRole = new UserRole();
userRole.setPortalUser(portalUser);
userRole.setRoleLookup(roleLookupRepository.findOne(dto.getId()));
userRole.setCreatedUpdatedBy(context.getName());
userRole.setCreatedUpdatedDate(createUpdateDate);
userRole.setAction(portalUser.getAction());
userRole.setStatus(portalUser.getStatus());
userRole.setActive(true);
portalUser.getUserRole().add(userRole);
}
portalUser.setActive(false);
portalUser = portalUserRepository.save(portalUser);
return portalUser;
I have see so many post, but this has not solved my issue. Any help is appreciated. Here the RoleLookup is a static table. Here is the exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.commerzbank.clearing.ccp.domain.UserRole.roleLookup -> com.commerzbank.clearing.ccp.domain.RoleLookup; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.commerzbank.clearing.ccp.domain.UserRole.roleLookup -> com.commerzbank.clearing.ccp.domain.RoleLookup
You should set a cascade = "save-update " for many-to-one side.

Resources