If I use #JsonView on a POJO and on a controller in Spring, my HATEOAS links are hidden. I can understand why this happens, because the _links property isn't annotated with the correct view class, but it's not the behaviour I need in this case. Is there a way to always include the _links property, regardless of using a view class or not?
My POJO looks something like:
#Entity(name = "groups")
#SequenceGenerator(name = "groups_groupid_seq", sequenceName = "groups_groupid_seq")
public class Group extends ResourceSupport {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "groups_groupid_seq")
#JsonView(Views.Summary.class)
private long groupID;
#JsonView(Views.Full.class)
private String name;
/*
Getters & setters ...
*/
}
And my controller looks something like:
#RestController
#RequestMapping(path = "/group")
#ExposesResourceFor(Group.class)
public class GroupApiController {
#JsonView(Views.Summary.class)
#RequestMapping(path = "/", method = RequestMethod.GET)
public Iterable getPermittedGroups(
Authentication authentication) {
// load groups...
}
}
My HATEOAS configuration:
#Configuration
#EnableAspectJAutoProxy
#EnableEntityLinks
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class HateoasConfig {
//
}
I don't have to use #JsonView, so I'm happy to use an alternative. But I do need to be able to return different views on the same class, from different controller methods, otherwise I could use #JsonIgnore.
Is there a way to always include the _links property, regardless of
using a view class or not?
Using Spring Boot
Set the spring jackson mapper configuration
spring.jackson.mapper.default-view-inclusion=true
Within your application.properties
Using Spring #Configuration Classes
This topic is well covered in "Configuring Object Mapper in Spring"
However, there are currently issues in particular with HATEOAS that prevent this from working. In particular is the open ticket "Support for HATEOAS-Links in Json-Views"
Related
Please note: while I would accept an XML-based solution if that's truly the only way to accomplish what I'm looking for, I would greatly prefer a solution using Dozer's Java API.
I am new to Dozer and am trying to figure out how to use its API. It seems to default to field-level mappings (if the field names match) and to allow for custom mappers and converters in the event that field-level mapping (based on field name) is either not possible or not logical for your application needs.
I have a situation where my app will take a DTO, say, ReportedIssue (an issue reported by a user and sent to my application over HTTP), and an Issue entity (a data entity that will be persisted to a MySQL DB).
Here are my two objects:
#Data
public class ReportedIssue {
private String typeRefId;
private String reporterRefId;
private String info;
}
#Entity
#Table(name = "issues")
#Data
public class Issue {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "issue_ref_id")
private String refId;
#Column(name = "issue_tracking_number")
private String trackingNumber;
#OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "issue_type_id", referencedColumnName = "issue_type_id")
private IssueType type;
#Column(name = "issue_reported_on")
private Date reportedOn;
#OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "issue_reporter_id", referencedColumnName = "account_id")
private Account reporter;
#Column(name = "issue_info")
private String info;
}
So in the application frontend, a user can report an issue. The frontend sends a JSON version of a ReportedIssue to the backend, where that JSON is deserialized into a ReportedIssue DTO bean. Then I need Dozer to convert my ReportedIssue into an Issue entity that I can then easily save to my MySQL DB.
Here is my best attempt:
public class ReportedIssueConverter extends DozerConverter<ReportedIssue, Issue> {
private AuthService authService;
public ReportedIssueConverter(AuthService authService, Class<ReportedIssue> prototypeA, Class<Issue> prototypeB) {
super(prototypeA, prototypeB);
this.authService = authService;
}
public ReportedIssueConverter(Class<ReportedIssue> prototypeA, Class<Issue> prototypeB) {
super(prototypeA, prototypeB);
}
#Override
public Issue convertTo(ReportedIssue source, Issue destination) {
Issue issue = new Issue();
issue.setRefId(UUID.randomUUID().toString());
issue.setType(IssueUtils.determineType(source));
issue.setReportedOn(DateTimeUtils.nowInUTC());
issue.setReporter(authService.currentUser());
issue.setInfo(destination.getInfo());
return issue;
}
#Override
public ReportedIssue convertFrom(Issue source, ReportedIssue destination) {
throw new UnsupportedOperationException("we currently don't map from issues to reported issues");
}
}
Several concerns here. For one, is such a custom converter even necessary? Or is there a "better" (more standards compliant or using generally-accepted Dozer practices) way to use the Dozer API to perform this conversion? But mainly, this DozerConverter seems to be intended for bi-directional mapping use cases. Whereas, in my application, I will never have an Issue instance and need to map it back to a ReportedIssue DTO instance. So I only need one-way mapping from ReportedIssue --> Issue. Am I using Dozer correctly by throwing an UnsupportedOperationException or is there another interface or API trick I can use to only leverage the one-way mapping I need?
It could actually be done without a custom converter using custom getter methods in your dto class corresponding to fields in Issue. Dozer works by mapping each field in destination class by trying to invoke the getter method of the corresponding name in the source class.
public class ReportedIssue {
// fields.......
public String getRefId() {
UUID.randomUUID().toString()
}
public IssueType getType() {
IssueUtils.determineType(this);
}
// similarly create getters for other required fields.
}
But for reporter field in Issue, you need an AuthService object. I would suggest writing a static method as below:
public static Issue getIssue(AuthService auth, ReportedIssue dto) {
Issue issue = //map using dozer
issue.setReporter(authService.currentUser());
return issue;
}
Gauntham answer will work. Another option:
Implement a com.github.dozermapper.core.BeanFactory
Your custom BeanFactory can handle
Issue issue = new Issue();
issue.setRefId(UUID.randomUUID().toString());
issue.setReportedOn(DateTimeUtils.nowInUTC());
issue.setReporter(authService.currentUser());
Then depending on your preferences, this could also go into the bean factory
issue.setType(IssueUtils.determineType(source));
Or you could handle that separately in the mapping. Something would need to know how to call IssueUtils, so that is either 1) a customer converter or 2) a change to the DTO or entity to have the functionality through a getter or setter.
Finally, this line would be handled in the Dozer Java API mapping
issue.setInfo(destination.getInfo());
Personally, I like Dozer's com.github.dozermapper.core.loader.api.BeanMappingBuilder where you can explicitly tell it how to map 2 beans, specify the bean factory to use and the custom converter for a specific field.
mapping(ReportedIssue.class, Issue.class, oneWay(), wildcard(true), beanFactory(IssueBeanFactory.class.getName()).fields("this", "type", customConverter(IssueTypeConverter.class)
oneWay(), wildcard(boolean), and beanFactory(String) are found in Dozer's TypeMappingOptions and customConverter(Class.class) is found in Dozer's FieldMappingOptions.
oneWay() makes the mapping work only in the direction specified in the BeanMappingBuilder.
wildcard(true) tells Dozer to automatically map matching fields (this is default behavior).
I have an JavaDTO like:
public class myDTO {
private String name;
private Integer age;
}
I want to do different validation in Micronaut by an CREATE operation and by an UPDATE operation. In Spring you can define different 'groups' therefore. See here stackoverflow link or here external link. So this looks like:
public class myDTO {
#Null(groups = OnCreate.class)
#NotNull(groups = OnUpdate.class)
private String name;
#Null(groups = OnCreate.class)
#NotNull(groups = OnUpdate.class)
private Integer age;
}
Is there something similiar for micronaut there?
I believe this is not Spring functionality but more how beans are validated from the javax bean validator.
You need to use Hibernate Validator where javax.persistence.validation.group.pre-update are applicable.
Default Micronaut bean validation is not using Hibernate Validator.
Try to add hibernate validator as dependency.
https://micronaut-projects.github.io/micronaut-hibernate-validator/latest/guide/index.html
I have en entity class which has an natural ID field mapped as #Id and I don't have any surrogate ID(invented field only for table ID) field. And, in the Jackson marshalled JSON I see an extra id exposed.
So instead of:
{
"bin":"123456", ...
}
I see:
{
"id":"123456", "bin":"123456", ...
}
which I don't want because they are repeated information. How can I prevent this?
I haven't touched REST/MVC configuration adapter; they are for exposing the ID classes, but I don't want that.
Bean:
#Entity
#Data
#Table(name="bin_info")
public class BinInfo implements Serializable, Persistable<String> {
#Id
#NotBlank //this is for absent parameter. Not equal to Pattern regex check
#Pattern(regexp = "^\\d{6,8}$") //6-8 digits
#Column(name="bin")
#JsonProperty("bin")
private String bin;
...
I am with these dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-aop')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-undertow')
runtime('com.h2database:h2')
runtime('org.postgresql:postgresql')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('io.cucumber:cucumber-java:3.0.2')
testCompile('io.cucumber:cucumber-junit:3.0.2')
testCompile('io.cucumber:cucumber-spring:3.0.2')
}
Spring Boot 2.0.3.
Try annotating with #NaturalId instead of #Id.
Thanks all, I think it has to do with some configuration of Spring or Jackson that will automatically expose the field mapped with #Id. I can just guess because no time for a confirmation.
And some colleague suggests me to define a DTO instead of putting the #Jsonxxx annotations in the class, saying the model represents data model and are related to the table, while DTO is related with view layer. So I did it and now all is fine.
Now the model is free of id field and #JsonProperty/#JsonIgnore:
#Entity
#Data
#Table(name="bin_info")
public class BinInfo implements Serializable, Persistable<String> {
#Id
#NaturalId
#NotBlank //this is for absent parameter. Not equal to Pattern regex check
#Pattern(regexp = "^\\d{6,8}$") //6-8 digits
#Column(name="bin")
//#JsonProperty("bin")
private String bin;
...
And the DTO is totally without #Id:
#Data
public class BinInfoDTO {
#JsonProperty("bin")
private String bin;
#JsonProperty("json_full")
private String json_full;
...
When I retrieve an entity, with a mapping method I set all values I need in a DTO to the DTO and return it to the endpoint. Then the JSON is normal and fine.
You have to implement the get and set from id field.
Try annotating that field with #JsonIgnore
I am learning Spring Boot and actually I am trying to fetch lazy loaded associations just when it is needed and it does not include the Json generated during the page rendering.
I have a class Person which have an auto-association "father":
#Entity(name = "Person")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long idPerson;
private String Name;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "idFather")
private Person father;
/*Getters and setters ommited*/
}
I have configured an object mapper with jackson as suggested by this site:
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(Hibernate5Module.class);
}
Finally I have a GET endpoint that will return a single Person if the id is informed or list everyone otherwise. My PersonRepository simple extends the CrudRepository interface.
When I try to fetch a single record the father is not loaded as expected:
//localhost:9000/person/2
{"idPerson":2,"name":"Luke Skywalker","father":null}
But if I try to retrieve all People (CrudRepository.listAll) the associations are fetched:
//localhost:9000/person
[
{
"idPerson":1,
"name":"Darth Vader",
"father":null
},
{
"idPerson":2,
"name":"Luke Skywalker",
"father":{
"idPerson":1,
"name":"Darth Vader",
"father":null
}
}
]
I do not desire this behavior and I am probably missing some configuration on the Object Mapper.
Does someone have an idea about what I should do?
Edit:
I dont think this is a duplication to Avoid Jackson serialization on non fetched lazy objects.
First: The answer provided there is old and WebMvcConfigurerAdapter is deprecated on Hibernate 5.
Second: Based on Configure Jackson to omit lazy-loading attributes in Spring Boot. I suspect the solution provided by #r1ckr on the first topic is actually equivalent to what I am using:
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(Hibernate5Module.class);
}
Third: Even knowing abot the deprecation I tried the solution proposed with the same behavior which reinforced the second point.
Fourth: I tried the same approach using the actual interface WebMvcConfigurer and again achieved the same behavior.
#Configuration
#EnableWebMvc
public class Configuration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonMessageConverter());
WebMvcConfigurer.super.configureMessageConverters(converters);
}
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter =
new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate5Module());
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
}
I just figured it out, some concepts about Hibernate was not fully understood by me and I imagined it was unnecessarily fetching the associations when retrieving all entities.
As I enabled the SQL log I could see the framework was issuing a single select statment. It makes sense: if I have all data in-memory, why not populate the associations early and avoid the trouble to fetch the data again on the future? Very clever indeed.
I want to use the same bean for both database persistance, and for a webservice json request.
Problem: I have a property that should never be set by the webservice user, but filled by the application before persistance. The field in the db is marked as not null, so the domain validation is: #NotNull.
But if I add this, then spring-mvc will also validate the incoming json request and throw an exception if id is not set (but which is desired).
Question: how can I exclude certain validation properties only on the mvc layer?
#Entity
public class Person {
#Id
#NotNull
#JsonIgnore //has no effect
private String id; //to be set by application before persistance
private String name;
private int age;
}
#RestController
public class PersonController {
#PostMapping
public void post(Person p) {
}
}
The requirement you've described is to be able to define different constraints for the same bean depending on the respective context. In the MVC layer you want id to be nullable and persistence layer you want to it to be not-null.
You can implement this requirement by using Validation Groups.
Your first introduce a new interface for the persistence validation case lets say:
public interface PersistenceGroup {
}
Then you change your bean to:
#Entity
public class Person {
#Id
#NotNull(groups = PersistenceGroup.class)
private String id; //to be set by application before persistance
private String name;
private int age;
}
#RestController
public class PersonController {
#PostMapping
public void post(Person p) {
}
}
This has the effect that #NotNull is only validated for the given group.Because by default the javax.validation.groups.Default group is used for validation, your MVC will not check for #NotNull. Now we tell Hibernate to use your group when its BeanValidationEventListener is activated in case of persisting, updating or deleting your entity. This can be done by setting the corresponding of the following properties (see docs):
javax.persistence.validation.group.pre-persist
javax.persistence.validation.group.pre-update
javax.persistence.validation.group.pre-remove
Example for your use case:
<hibernate-configuration>
<session-factory>
...
<property name="javax.persistence.validation.group.pre-persist">
javax.validation.groups.Default,your.package.PersistenceGroup
</property>
<property name="javax.persistence.validation.group.pre-update">
javax.validation.groups.Default,your.package.PersistenceGroup
</property>
...