#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 {}
Related
I am creating REST Service with spring boot and defined some classes with inheritence, however I'm not able to receive a JSON payload which I am sending from postman to the controller.
JSON Payload which I'm sending :
{
"dummy" : "okok",
"fullName": "okok",
"mobileNumber": 1234567890
}
I am only getting dummy property in the controller, rest of the properties not getting mapped to POJO.
Logging statement prints following line on the console
ownerAccount OwnerAccount(dummy=okok)
I think only OwnerAccount constructor is getting invoked and Account properties not getting initialized.
Please help me understand the missing part or mistake I am doing here.
I have defined following structure :
Account.java
#NoArgsConstructor
#AllArgsConstructor
#Data
public class Account {
#NotBlank(message = "fullName is mandatory")
private String fullName;
#NotNull(message = "mobileNumber is mandatory")
private Long mobileNumber;
#Valid
private AddressRequest addressRequest;
}
OwnerAccount.java
#NoArgsConstructor
#Data
public class OwnerAccount extends Account {
#NotBlank(message = "dummy is mandatory")
private String dummy;
}
OwnerController.java
#RestController
#RequestMapping(path = "/api/v1/account/owner")
public class OwnerAccountResource {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private OwnerAccountService ownerAccountService;
#Autowired
public OwnerAccountResource(OwnerAccountService ownerAccountService) {
this.ownerAccountService = ownerAccountService;
}
#PostMapping("/signup")
public ResponseEntity createOwnerAccount(#RequestBody #Valid OwnerAccount ownerAccountRequest) {
logger.info("ownerAccountDto {}", ownerAccountRequest);
return ResponseEntity.ok(ownerAccountService.createAccount(ownerAccountRequest));
}
}
I suppose you think you are not able to receive dummy field because here you are printing only Account fields because #Data add #ToString annotation but it's not printing super class fields
logger.info("ownerAccountDto {}", ownerAccountRequest);
but if you could debug that controller you would see dummy field is there. You need to override toString() to log dummy field or just add lombok annotation
#NoArgsConstructor
#Data
#ToString(callSuper = true)
public class OwnerAccount extends Account {
#NotBlank(message = "dummy is mandatory")
private String dummy;
}
#ToString(callSuper = true) will include fields from super class
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.
Please note that I have looked at similar questions and I have explained why they haven't worked for me
I have a simple Spring boot JPA-Hibernate application with one to one mapping between User and Address. (Please note that I do not have this issue with one to many mapping)
User Entity
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Address address;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Note> notes;
}
Address Entity
#Entity
#Table(name = "addresses")
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String street;
#Column
private String city;
#JsonIgnore
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
Note Entity
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
My problem is that whenever I call the controller mapped to get all users I was getting the address and all the associated notes with it as well. But I would expect FetchType.LAZY to take care of that.
I read a lot of questions on StackOverflow mentioning that Jackson might be the culprit here:
Post 1
I also read that spring.jpa.open-in-view defualt value might be the culprit:
Post 2
Post 3
So i tried the following options:
I disabled default open in view property by adding spring.jpa.open-in-view=false to my application.properties which started giving me
Could not write JSON: failed to lazily initialize a collection of role error
I am assuming its because Jackson is calling the getters on my lazily loaded objects so I followed the instructions from another post and added the following for Jackson to leave the lazily loaded collections alone:
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.9</version>
</dependency>
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
This solution above fixed the issue with the One to Many mapping but still has the Address associated in the response.
I am not sure what can I do here. The User Entity on the default landing page does not need any address details so I do not want to load it on the landing page. When the record is clicked then it navigates to another page and that's where I would like all the lazy loaded objects to be returned in the response.
I have tried everything I could find online but still nothing has worked so far. I would really appreciate some help with this.
As mentioned by one of the users that it might a duplicate of another question on SO:
Suggested Possible duplicate
I would like to mention that I got the Lazy loading working by disabling spring.jpa.open-in-view property but adding
mapper.registerModule(new Hibernate5Module());
brings back the address associated to the User in the response.
It's working as in the JPA spec:-
Refer the below URL
https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FetchType.html
LAZY fetching strategy is only a hint (as the javadoc says the data can be lazily fetched).. not a mandatory action.
Eager is mandatory (as the javadoc says the data must be eagerly fetched).
You may take a look at Jackson Serialization Views.
I´ve taken a look into the Hibernate5 module you tried and it has some interesting features... but none should fix this issue out of the box for you.
By the way, I normally fix this issue by not returning the Entity as the response but DTOs instead.
The problem is jackson triggering initialization when he writes the JSON, so just don't write the current field (address). But you should not use #jsonIgnore so at other places you could return an Eager obj.
You can use the #jsonView annotation that can provide different JSON for the same obj at different requests. You can look this example :
Create view class:
public class ViewFetchType {
static class lazy{ }
static class Eager extends lazy{ }
}
Annotate your Entity
#Entity
public class User {
#Id
#JsonView(ViewFetchType.Lazy.class)
private String id;
#JsonView(ViewFetchType.Eager.class)
#OneToOne( fetch = FetchType.LAZY)
private Address address ;
}
Specify the FetchType class in your controller:
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping("get-user-details")
#JsonView(ViewFetchType.Eager.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
{
#RequestMapping("get-all-users")
#JsonView(ViewFetchType.Lazy.class)
public #ResponseBody List<User> getUsers() {
return userRepository.findAll();
}
}
Here is the answer that i took the idea from... https://stackoverflow.com/a/49207551/10162200
I'm migrating my Spring Boot REST API from 1.5.4 to 2.0.3.
These are my two entities, a repository for one of them and a controller for accessing them:
Parent.java
#Entity
#Table(name = "PARENT")
public class Parent implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Child> children;
}
Child.java
#Entity
#Table(name = "CHILD")
public class Child implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Column(name = "PARENT_ID")
private Long parentId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Parent parent;
#Column(name = "name")
private String name;
}
ParentRepository.java
public interface ParentRepository extends JpaRepository<Parent, Long> {
}
ParentController.java
#RestController
#RequestMapping("/parents")
public class ParentController {
#Autowired
private ParentRepository parentRepository;
#RequestMapping(method = RequestMethod.GET)
public List<Parent> getParents() {
return parentRepository.findAll();
}
}
It appears that there is no longer an active session in the #RestController classes since
parentRepository.findAll().get(0).getChildren().get(0).getName();
now throws a
LazyInitializationException: failed to lazily initialize a collection of role: com.mycompany.myapplication.entity.Parent.children, could not initialize proxy - no Session
This can be fixed by setting a #Transactional annotation on either the controller method or the controller class.
However, the problem I have regards the lazily loaded children.
If I run the example code above, even with the #Transactional annotation, I get the same exception but with a nested
com.fasterxml.jackson.databind.JsonMappingException
This is due to the serialization to JSON happens outside of the controller, hence outside the active session.
There is an ugly fix for this, by reading some data from each child before exiting the method:
#RequestMapping(method = RequestMethod.GET)
public List<Parent> getParents() {
List<Parent> parents = parentRepository.findAll();
parents.stream()
.flatMap(p -> p.getChildren().stream())
.forEach(Child::getName);
return parents;
}
This works, but is terribly ugly and adds a lot of boilerplate.
Another solution would be to map all entities to DTOs before returning them to the client. But this solution adds another layer to my application which I don't want.
Is there a way to make sure that there is an active session during the automagical serialization of the entities?
Soo yeaah...
During migration I had previously set
spring.jpa.open-in-view = false
because I saw a new warning about it in the log. This setting removes the active session I wanted help adding...
Removing this setting and using the default (true) fixed my problem entirely.
I'm rewriting some messy code that manages a database, and saw that the original programmer created a class mapped to the database like so:
(I've removed unnecessary code that has no purpose in this question)
#Entity
#Data
#EqualsAndHashCode(callSuper = false, of = { "accessionCode", "header", "date" })
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
protected PDBEntry(){}
public PDBEntry(String accessionCode, String header, Date date){
this.accessionCode = accessionCode;
this.header = header;
this.date = date;
}
}
I am still a beginner at Hibernate and using Lombok, but wouldn't this do the same thing and wouldn't Lombok automatically create the needed constructor for you?
#Entity
#Data
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#NonNull
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
#NonNull
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
}
Also, the original programmer of this code says he allows for the header to be 'null', yet he explicitly created a constructor that needs a value for header. Am I missing something or is this a bit contradictory?
Have a look at #NoArgsConstructor, #RequiredArgsConstructor, #AllArgsConstructor.
The constructor behavior of #Data is like #RequiredArgsConstructor:
#RequiredArgsConstructor generates a
constructor with 1 parameter for each
field that requires special handling.
All final fields get a parameter, as
well as any fields that are marked as
#NonNull that aren't initialized where
they are declared.
Given that none of your fields are either final or #NonNull, this will result in a no-argument constructor. However, this is not the most expressive way to achieve this behavior.
What you'll probably want in this case is a #NoArgsConstructor (optionally combined with a #AllArgsConstructor), to clearly communicate the intended behavior, as is also indicated in the documentation:
Certain java constructs, such as
hibernate and the Service Provider
Interface require a no-args
constructor. This annotation is useful
primarily in combination with either
#Data or one of the other constructor
generating annotations.
That bit is contradictory you're right. I've not used Lombok before but with hibernate if you want to be able to create a bean and persist you need the default constructor as given above as far I was aware. It uses Constructor.newInstance() to instantiate new objects.
Here is some hibernate documentation which goes into more detail.
Hibernate Documentation
If you are using #Data with a #NonNull field and still want a noargs-constructor, you might wanna try to add all 3 annotation together
#NoArgsConstructor
#RequiredArgsConstructor
#AllArgsConstructor
Apparently an old intelliJ bug which I did replicate in Eclipse Kepler and lombok v0.11.4
#NoArgsConstructor,
#RequiredArgsConstructor,
#AllArgsConstructor
Generate constructors that take no arguments, one argument per final / non-null field, or one argument for every field. Read this lombok-project
#Data
#RequiredArgsConstructor /*Duplicate method Someclass() in type Someclass*/
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true) /*Duplicate method Someclass() in type Someclass*/
#Entity
public class Someclass {
#Id
private String id;
private String name;
private Type type;
public static enum Type { X , Y, Z}
}
Fixed it by making member variables final
#Data
#RequiredArgsConstructor
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
#Entity
public class Someclass {
#Id
private final String id;
private final String name;
private final Type type;
public static enum Type { X , Y, Z}
}