I am having a StackOverflowError Error when I am trying to map data from neo4j into Spring data. The project is using Lombok.
I am able to get Project with only one Use, That worked fine. But for a Project with two or more use, The error occur.
Can anyone help? Thanks.
Error:
Resolved exception caused by Handler execution: org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
Project.java
#NodeEntity
#Data
//#JsonIgnoreProperties({"project","use"}) //also tried but same issue
public class Project {
#Id
private String id;
#Nullable
private String name;
#JsonIgnoreProperties({"project","use"})
#Relationship(type = "USED_FOR")
List<ProjectUseRelation> usedFor = new ArrayList<>();
}
Use.java
#NodeEntity
#Data
//#JsonIgnoreProperties({"project","use"}) //also tried but same issue
public class Use {
#Id
private String id;
#Nullable
private String name;
#JsonIgnoreProperties({"project","use"})
#Relationship(type = "USED_FOR", direction = Relationship.INCOMING)
List<ProjectUseRelation> usedByProjects = new ArrayList<>();
}
ProjectUseRelation.java
#RelationshipEntity(type = "USED_FOR")
#Data
public class ProjectUseRelation {
#Id
private Long id;
#Nullable
private String name;
#StartNode
#JsonIgnoreProperties({"usedFor"})
private Project project;
#EndNode
#JsonIgnoreProperties({"usedByProjects"})
private Use use;
}
Service.java
#Service
#AllArgsConstructor
public class Service{
private final Repository repository;
#Transactional(readOnly = true)
public Map<String, Object> graph(String id){
//system correctly reached here
Collection<Project> result = repository.graph(id);
//system can't reach here
return result;
}
}
Repository.java
public interface Repository extends Neo4jRepository<Project,String>{
#Query("MATCH map = (p:Project)-[]-(u:Use) WHERE p.id = {id} RETURN map")
Collection<Project> graph(#Param("id") String id);
}
This now can return me single/multiple on-to-one relationships, but not single one-to-many relationship.
If you need to serialize your data to JSON, and your entities have circular dependencies (e.g., entity X has a field that references Y, and Y has a field that references X), then you must use the annotations #JsonIgnoreProperties or #JsonIgnore, where appropriate, to avoid StackOverflowErrors during serialization.
Refer to the documentation for more details.
[UPDATE]
Your Project entity ignores the ProjectUseRelation.project field (during serialization) but does not ignore ProjectUseRelation.use.
And the Use entity ignores ProjectUseRelation.use but does not ignore ProjectUseRelation.project.
Therefore, a circular dependency still exists:
Project.userFor
-> ProjectUseRelation.use
-> Use.usedByProjects
-> ProjectUseRelation.project
-> Project
So, I figured out that the issue is with Lombok #Data annotation. If I comment out #Data annotation and write my own getters and setters, there will not be a Stack Overflow Error.
Related
While building the project, I'm constantly receiving the issue as:
java: incompatible types: com.test.application.data.models.Project cannot be converted to java.util.function.Predicate<? super com.test.application.data.models.Project>
I have the following Entity class:
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity(name = "project_entity")
#Table(name = "projects")
public class Project {
public Project(ProjectDTO projectDTO) {
this.name = projectDTO.getName();
this.abbreviation = projectDTO.getAbbreviation();
this.customer = projectDTO.getCustomer();
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "project_id")
private Integer id;
private String name;
private String abbreviation;
private String customer;
}
And I have service implementation:
#Service
public class ProjectServiceImpl implements ProjectService {
#Autowired
private ProjectRepository projectRepository;
#Autowired
private ProjectAdaptor projectAdaptor;
#Override
public List<Project> getAllProjects(Long projectId) {
return projectRepository.findAll().stream().filter(((Project) project ->
project.getId() == projectId)) .findAny().orElse(null);
}
#Override
public Project save(Project project) {
return projectRepository.save(project);
}
#Override
public List<ProjectDTO> findAllProducts() {
List<Project> project = projectRepository.findAll();
return projectAdaptor.databaseModelToUiDtoList(project);
}
}
Based on it my question is how can I solve the issue related to:
java: incompatible types: com.test.application.data.models.Project cannot be converted to java.util.function.Predicate<? super com.test.application.data.models.Project>
?
I'm ready to provide more details, if it helps.
I'm trying to get an id while rebuilding the project, but this error appears. I've researched some fresh information, but with no luck.
Thanks in advance for any help with it.
In the lambda expressions in the filter you CAN express the argument type like this:
(Project project) -> ....
but it's really not needed here.
Not specific to the question there are two other points to fix here:
doing projectRepository.findAll() and then filtering for id is a waste of resources, you'll load in memory ALL rows in that table and then discard all except one. Your ProjectRepository interface will probably has a findById method just for what you want
the comparison project.getId() == projectId between two Long objects needs to be expressed using equals project.getId().equals(projectId). You can use == if they where long primitive type and not objects.
#BasePathAwareController
public class MetricController {
#Autowired
private MetricRepository metricRepository;
#Transactional
#RequestMapping(method = RequestMethod.GET, value = "/metrics/in/{id}")
public #ResponseBody
MetricDTO getMetric(#PathVariable Long id) {
return MetricDTO.fromEntity(metricRepository.getOne(id));
}
}
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(
uniqueConstraints = #UniqueConstraint(columnNames = {"metricType", "instanceType"}, name = "customUniqueId")
)
public class Metric implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
private SourceType sourceType;
private String metricTypeField;
private String metricType;
private String instanceType;
private String instanceTypeField;
#ElementCollection
private List<String> metricIdFields;
#ElementCollection
private List<String> valueFields;
#ElementCollection
private Map<String, String> virtualFieldValueEx;
}
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class MetricDTO {
private SourceType sourceType;
private String metricTypeField;
private String metricType;
private String instanceType;
private String instanceTypeField;
private List<String> metricIdFields;
private List<String> valueFields;
private Map<String, String> virtualFieldValueEx;
public static MetricDTO fromEntity(Metric metric) {
return new MetricDTO(
metric.getSourceType(),
metric.getMetricTypeField(),
metric.getMetricType(),
metric.getInstanceType(),
metric.getInstanceTypeField(),
metric.getMetricIdFields(),
metric.getValueFields(),
metric.getVirtualFieldValueEx()
);
}
}
Since #RepositoryRestController in Spring Data Rest is not compatible with Swagger, I changed it to #BasePathAwareController.
So, the problem is that the controller is not working properly.
The error history is as follows.
Could not write JSON: failed to lazily initialize a collection of role: com.jmsight.management.entity.Metric.metricIdFields, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.jmsight.management.entity.Metric.metricIdFields, could not initialize proxy - no Session (through reference chain: com.jmsight.management.dto.MetricDTO["metricIdFields"])
Using #RepositoryRestController works normally.
What is the problem? Is it resolvable?
If you check the source of #BasePathAwareController then you can see that it's not annotated with #Controller.
So if a class is annotated only with #BasePathAwareController then it won't be picked neither by the default SpringMvc neither RepositoryRestMvc.
The former picks classes annotated with #Controller or #RequestMapping, the latter picks only classes annotated with #RepositoryRestController.
So once again, just to make it clear: #BasePathAwareController is NOT an 'extension' of #Controller, it's just a additional 'sign' annotation.
You can use #Controller together with #BasePathAwareController instead of #RequestMapping too.
I believe it's a misleading naming, or simply a bug in the implementation.
One more thing.
If you switch the from #RepositoryRestController to #Controller/#RequestMapping, then your controller will be handled a completely different way.
It could look that it works the same way, but it's called by a completely different handlerMapping: which uses different converters, argumentResolvers, even a different objectMapper.
There could be unpleasant surprises if you need to implement more complicated handler-methods in your controller-class.
I solved it.
To share, #BasePathAwareController should be written in class as #RequestMapping.
I don't know why. If you know reason teach me please.
#BasePathAwareController
#RequestMapping(value = "your url value")
public class MetricController {}
I’m starting my very first steps with Mapstruct mapper. I want to map a JPA data entity class to a DTO class. This is my source class:
#Entity
#Data
#Table(name = "projects")
public class Project {
#Id
private Long Id;
private String projectName;
private String description;
#OneToMany(mappedBy = "project")
List<Sprint> sprints;
#OneToMany(mappedBy = "project")
List<Epic> epics;
#OneToMany(mappedBy = "project")
List<Story> stories;
public Project(Long id, String projectName, String description) {
Id = id;
this.projectName = projectName;
this.description = description;
}
}
This is my target class:
#Data
#AllArgsConstructor
public class ProjectDTO {
private Long Id;
private String projectName;
private String description;
}
The #Data annotation is from Lombok.
I want to make a mapper to map the Project to ProjectDTO, the attributes like sprints, epics, stories SHOULD NOT be included in ProjectDTO. This is my mapper interface:
#Mapper
public interface ProjectMapper extends Mapper {
ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class)
ProjectDTO projectToProjectDTO(Project project);
}
When I try to build it, this is the error message I got:
[ERROR] Can't generate mapping method with no input arguments.
I guess it’s related to the missing properties in ProjectDTO, but don’t know to solve it. With the #Mapping, I cannot do it like:
#Mapping(source=“sprints”, target= null)
Any help would be appreciated!
Add the '#NoArgConstructor' as well. MapStruct cannot (yet) deal with constructing objects via constructor. Another option would be using '#Builder' in stead if your objects are truly immutable
You should not extend the annotation Mapper. It is enough when you just use it at the type declaration level of your interface
I'm seeing some videos about API Rest with Spring Boot and so far I've done some basics and when I tried to increase the complexity I'm getting caught.
My idea is in the Post / class, create a new class with students getting the following json:
{
"nome": "Primeira Serie - A".
"alunos": [
"João",
"José",
"Maria"
]
}
And return:
{
"id_classe": 101
}
It happens that it saves the class, but it does not save the students and I have no idea how to show only the id of the class.
I have created the following classes in Java:
Model
Classe.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "classe")
public class Classe {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#OneToMany(mappedBy = "classe")
private Set<Aluno> alunos = new HashSet<Aluno>();
//Get's e Set's suppressed
}
Aluno.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "aluno")
public class Aluno {
private static int tempID = 0;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#ManyToOne
#JoinColumn(name = "id_classe")
#JsonBackReference
private Classe classe;
public Aluno(String nome) {
tempID++;
this.id = tempID;
this.nome = nome;
}
public Aluno() {
}
//Get's e Set's suppressed
}
Repository
ClasseRepository.java
package com.example.classe.repository;
//Import's suppressed
#Repository
public interface ClasseRepository extends JpaRepository<Classe, Integer> {
public List<Classe> findAll();
}
Controller
ClasseController.java
package com.example.classe.controller;
//Import's suppressed
#RestController
#RequestMapping("/classe")
public class ClasseController {
#Autowired
private ClasseRepository classeRepo;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Collection<Classe>> getClasse() {
return new ResponseEntity<>(classeRepo.findAll(), HttpStatus.OK);
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> salvarClasse(#RequestBody Classe classe) {
return new ResponseEntity<>(classeRepo.saveAndFlush(classe), HttpStatus.CREATED);
}
}
Am I doing everything wrong or did I not understand the concept? But I wanted to understand how to do it that way.
Thanks in advance.
Cesar Sturion
What you want to achieve is totally doable, but requires several changes.
I split my answer into 2 parts:
Save the students
There are several problems with saving:
On POST your incoming json deserialized into objects in which Classe has a reference to Anuli, but Anuli doesn't have a reference toClasse. To check it you can add a break point at the line: return new ResponseEntity<>(... , run in debug mode and check fields of Anuli in Classe. To fix it you can add #JsonManagedReference on aluni field in Classe. Related question
Hibernate can't save referenced objects by default. You have to save them one by one after saving your Classe object or just turn on Cascade persisting. Related question
So, to fix 1 and 2 Classe should have:
#OneToMany(mappedBy = "classe", cascade = CascadeType.PERSIST)
#JsonManagedReference
private Set<Aluno> alunos = new HashSet<Aluno>();
You have to remove custom id generation in Alumi (I am talking about static int tempID). Annotation #GeneratedValue will perfectly generate id for you as soon as you persist an object. This custom generation breaks Hibernate support. I even not talking about that it also breaks the app after restart, not threadsafe etc.
Return id only
On POST returned json represent what was returned in classeRepo.saveAndFlush(classe) so it's an object of Classe.
If you want to return exactly this:
{
"id_classe": 101
}
Then create new class like this:
public class ClasseIdVO {
#JsonProperty("id_casse")
private Integer id;
// Constructors, getter, setter
VO - means View Object, so this object only for representation, not for persisting, etc.
You can use field name id_casse, but it's against Java code convention, so better add #JsonProperty.
Also change your saving code to new ClasseIdVO(classeRepo.saveAndFlush(classe).getId())
Or you can just return id as a number: classeRepo.saveAndFlush(classe).getId()
I'm using a JPA query that uses a specification to retrieve entities. When I execute the query, I'm getting the error:
org.springframework.data.mapping.PropertyReferenceException: No property name found for type Task!
I've looked at the answers to similar questions that have been asked on this site previously & tried to model my code to follow the patterns that were recommended but the code is still failing.
When I step through the code with a debugger, the expanded path in the criteria builder is returning the embedded ID class, but when the specification is actually used in the query it looks like the attribute is being applied to the base entity class.
Am I missing something obvious?
Here is the entity class:
#Entity
#Table(name = "TASKS")
public class Task implements Serializable {
#EmbeddedId
private TaskId id;
...more attributes, getters and setters
}
Here is the embedded ID entity class:
#Embeddable
public class TaskId implements Serializable {
#Column(name = "NAME", length = 100)
private String name;
...more attributes, getters and setters
}
Here is the specification builder that matches on the embedded id 'name' attribute:
public class HasTaskNameSpec {
private HasTaskNameSpec() {
}
public static Specification<Task> equals(String name) {
return (root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get("id").get("name"), taskName);
};
}
}
The query is executed on the repository as follows:
List<Task> results = taskRepository.findAll(HasTaskNameSpec.equals("foo"));
The repository itself is very simple:
public interface TaskRepository extends JpaRepository<Task, TaskId>, JpaSpecificationExecutor<Task> {
List<Task> findByIdName(String name);
Page<Task> findByIdName(String name, Pageable page);
}
** EDIT added methods to repository as was suggested below **
Ahh, the root cause was totally in our codebase. There was a sort order being specified on the page that didn't include the embedded "id" attribute. The above code works.
'root.get({embeddedIdName}).get({subPropertyName})' is used to query on embeddedId using specification.
#Embeddable
public class ProjectId implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "PROJECT_NAME")
private String projectName;
#Column(name = "ORGANIZATION")
private String organization;
......
......
}
#Entity
#Table(name = "projects")
public class Project {
#EmbeddedId
private ProjectId projectId;
#Column(name = "STARTED_TIME")
private Timestamp startedTime;
#Column(name = "ACTIVE")
private String active;
#Column(name = "DESCRIPTION")
private String description;
......
......
}
In the above snippet, ProjectId is an embedded id. To query on projectName, we should use below snippet.
expression = root.get("projectId").get("projectName");
Demo application link.
Take a look at this link which has a similar query.
EmbbededId Lookup
The final answer suggests that you can add a method to your TaskRepository thus.
public interface TaskRepository extends JpaRepository<Task, TaskId>, JpaSpecificationExecutor<Task> {
public List<Task> findByIdName(String name);
}