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

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"})

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

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

Get Jackson to ignore some fields at controller level in Spring Boot

I have the following class:
#Entity
#Table(name = "Positions")
#NamedQuery(name = "Position.findAll", query = "SELECT p FROM Position p")
public class Position implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "id")
private Long id;
private String title;
private String description;
#ElementCollection
#CollectionTable(name = "qualifications", joinColumns = #JoinColumn(name = "position_id"))
#Column(name = "required_qualifications")
private List<String> requiredQualifications;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "positions_competencies", joinColumns = #JoinColumn(name = "position_id"), inverseJoinColumns = #JoinColumn(name = "Competence_id"))
private List<Competence> competencies;
#ManyToOne
#JoinColumn(name = "department_id")
private Department department;
I'm using spring boot with spring data.
I want to ignore some of the fields in the getAllPositions method, but not in the getPositionById method, so #JsonIgnore won't work for me. What is the best way to do this in Spring Boot 2.0.2?
Here is the controller:
#Autowired
private PositionRepository positionRepository;
#GetMapping(path = "/positions")
public Iterable<Position> getAllPositions() {
return positionRepository.findAll();
}
#GetMapping(path = "/positions/{id}")
public Position getPositionById(#PathVariable Long id) {
return positionRepository.findById(id).get();
}

Issue with JPA. When fetchin eager parent all of its lazy children are also fetched [duplicate]

This question already has answers here:
How can I make a JPA OneToOne relation lazy
(12 answers)
Closed 4 years ago.
I am making a spring-boot project and currently I am facing a problem with lazy fetching. I have three classes one that is fetching its children with eager(Incident) and two that are with lazy(FileContent, FileStorage). The structure is:
Incident(1) -> (m)FileContent(1) -> (m)FileStorage.
Whenever I fetch from Incidet all of the fileContents and all of the fileStorages are also fetched. This should not be happening, fileStorages should not be fetched. It would be great if someone could tell me why the code behaves like this and help me fix It.
These are the classes:
#Getter
#Setter
#Entity
public class Incident extends BaseEntity {
#JsonIgnore
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "incident", orphanRemoval = true)
private List<FileContent> fileContent;
}
#Entity
#Getter
#Setter
public class FileContent extends BaseEntity {
#Column
private String fileName;
#OneToOne(mappedBy = "fileContent", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private FileStorage fileStorage;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "incident_id")
#LazyCollection(LazyCollectionOption.TRUE)
private Incident incident;
}
#Getter
#Setter
#Entity
public class FileStorage extends BaseEntity {
#Lob
#Column
private byte[] content;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "file_content_id")
private FileContent fileContent;
}
#Service(value = "ins")
public class IncidentService {
public Page<Incident> findAll(Pageable pageable) {
Page<Incident> incidents = incidentRepository.findAll(pageable);
if (CollectionUtils.isEmpty(incidents.getContent())) {
return null;
}
return incidents;
}
}
And this is the yml properties file
application.yml
- open-in-view: false
spring boot jpa java
Changed #OneToOne(fetch = FetchType.LAZY), to #OneToOne(optional = false, fetch = FetchType.LAZY) and it worked fine.

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