JPA throws lazy loading exception even it is eager fetch - spring-boot

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;
}

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

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;

Mapping DTO to existing entity , it is creating new entity

I have Activity class and ActivityDTO as you see below. While saving new entity there is no problem, but if I want to update existing entity, then it is creating new entity although my entity id is from my database entity.
#Getter
#Setter
#Entity
public class Activity implements Serializable {
#Id
#Column(name = "ID")
#GeneratedValue
#SequenceGenerator
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval=true)
#JoinTable(name = "ACTIVITY_ATTACHMENT", joinColumns = {
#JoinColumn(name = "ACTIVITY_ID") }, inverseJoinColumns = { #JoinColumn(name = "ATTACHMENT_ID") })
private Set<Attachment> attachments = new HashSet<>();
#Temporal(TemporalType.DATE)
#JsonSerialize(using = CustomJsonDateSerializer.class)
#JsonDeserialize(using = CustomJsonDateDeserializer.class)
#Column(name = "BEGIN_DATE")
private Date beginDate;
#Temporal(TemporalType.DATE)
#JsonSerialize(using = CustomJsonDateSerializer.class)
#JsonDeserialize(using = CustomJsonDateDeserializer.class)
#Column(name = "END_DATE")
private Date endDate;}
And my ActivityDTO
#Getter
#Setter
public class ActivityDTO {
private Long id;
private Set<Attachment> attachments = new HashSet<>();
#Temporal(TemporalType.DATE)
#JsonSerialize(using = CustomJsonDateSerializer.class)
#JsonDeserialize(using = CustomJsonDateDeserializer.class)
private Date beginDate;
#Temporal(TemporalType.DATE)
#JsonSerialize(using = CustomJsonDateSerializer.class)
#JsonDeserialize(using = CustomJsonDateDeserializer.class)
private Date endDate;
And here is my ActivityController class;
public Activity save(ActivityDTO activityDTO, List<MultipartFile> fileList) throws Exception {
Activity activity = convertActivityDTOtoEntity(activityDTO);
activity.getAttachments().addAll(ObjectFactory.createAttachment(fileList, Activity.class));
return activityRepository.save(activity);
}
public Activity update(ActivityDTO activityDTO, List<MultipartFile> fileList) throws Exception {
Activity activity = convertActivityDTOtoEntity(activityDTO);
activity.getAttachments().addAll(ObjectFactory.createAttachment(fileList, Activity.class));
return activityRepository.save(activity);
}
private Activity convertActivityDTOtoEntity(ActivityDTO activityDTO) {
return modelMapper.map(activityDTO, Activity.class);
}
Also I have one more problem, I have just transformed my entity usage to DTO objects, until now service was reaching entity directly and while updating if I delete any attachment or add, there was no problem. After I transformed to DTO objects and used like above, there is a problem while updating;
detached entity passed to persist: com.thy.agencycrm.entity.Attachment
And here is my Attachment entity if you would like to see;
#Getter
#Setter
#Entity
public class Attachment implements Serializable {
#Id
#Column(name = "ID")
#GeneratedValue
#SequenceGenerator
private Long id;
#Column(name = "MIME_TYPE")
private String mimeType;
Please help me about this problem, I am searhing and trying to solve it for long times.
Thanks you in advance.
I think you just copy the fields into a new object in your converter right?
Default JPA only update the entity if it is in the persistance context and the two object are identical. If you have a detached object, create a new one with in the converter, it will be saved as new record. It does not matter if you set the ID, because the id is generated by the sequence, as you annotated on the entity class.
You can resolve this many ways. The easiest is to load the entity by id, and set the fields from the another object into this managed object.
Updated your Class ActivityController
public Activity save(ActivityDTO activityDTO, List<MultipartFile> fileList) throws Exception {
Activity activity = convertActivityDTOtoEntity(activityDTO);
activity.getAttachments().addAll(ObjectFactory.createAttachment(fileList, Activity.class));
return activityRepository.save(activity);
}
public Activity update(ActivityDTO activityDTO, List<MultipartFile> fileList) throws Exception {
Activity activity = activitiyRepository.findOne(activityDTO.getID());
// This will update the existing activity with activityDTO
modelMapper.map(activityDTO, activity);
activity.getAttachments().addAll(ObjectFactory.createAttachment(fileList, Activity.class));
return activityRepository.save(activity);
}
private Activity convertActivityDTOtoEntity(ActivityDTO activityDTO) {
return modelMapper.map(activityDTO, Activity.class);
}

Why does #EntityGraph not load EAGER scince Spring version 2.2.5?

Before Spring Boot 2.2.5 #EntityGraph used to load EAGER, yet after 2.2.5 I need to add EAGER to the attributePaths, for example attributePaths = {"image", "roles"}
How #EntityGraph works or am i doing something wrong. The issue came up as I changed to the newer version 2.2.4 -> 2.2.5
#Entity
#Getter
#Setter
public class Employee {
#Column
private String email;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "employees_roles",
joinColumns = #JoinColumn(name = "employees_id", nullable = false, referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "roles_id", nullable = false, referencedColumnName = "id")
)
private Set<Role> roles;
#JoinColumn(name = "image_id")
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Image image;
}
#Repository
interface EmployeeRepository extends JpaRepository<Employee, Long> {
#EntityGraph(attributePaths = "image")
Optional<Employee> findByEmailIgnoreCase(String email);
}
#RestController
#RequiredArgsConstructor
#RequestMapping(value = "/employee", produces = MediaType.APPLICATION_JSON_VALUE)
public class EmployeeController {
private final EmployeeService employeeService;
#GetMapping(value = "/login")
public ResponseEntity<String> login(Principal user) throws IOException {
Employee employee = employeeService.findByEmailIgnoreCase(user.getName())
.orElseThrow(() -> new UsernameNotFoundException(USER_NOT_FOUND));
return ResponseEntity.ok(employee);
}
}
Your issue is related to a change included in Hibernate 5.4.12.Final which is included in Springboot 2.2.5.
See https://github.com/hibernate/hibernate-orm/pull/3164/files
To avoid this issue, you need to use the QueryHints. (javax.persistence.fetchgraph or javax.persistence.loadgraph)
attributePaths of #EntityGraph takes String[] you are using String
Try this way
#EntityGraph(attributePaths = {"image"})

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.

Resources