Spring Boot + Hibernate: Not fetch lazy-load relationships during serialization of collections - java

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.

Related

One way mapping in Dozer using custom converter

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

Evaluate lazily with spring-data

I would like to have the roles set fetch lazily. Hibernate fetchType.Lazy doesn't work for this cases, where spring data is used. I 've been trying lots of possibilities like Entitygraph but none of them works on this, or I'm using them wrong.
I have the next classes:
A class User:
#Entity
#JsonRootName(value = "user")
#Table(name = "web_users", schema = "t_dw_comercial")
public class User {
#Id
private int userId;
private String fullName;
#OneToMany(fetch=FetchType.LAZY)
#JoinTable(name="web_users_roles",
joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")}
)
private List<Role> roles;
}
A class Role:
#Entity
#JsonRootName(value = "roles")
#Table(name = "web_roles", schema = "t_dw_comercial")
public class Role {
#Id
private int roleId;
private String roleName;
}
Service:
#Service
public class UserService implements IUserService{
#Autowired
UserRepository repository;
public User findUserByLdapId(String loginName) {
return repository.findUserByLdapId(loginName);
}
}
Repository:
#Repository
public interface UserRepository extends CrudRepository<User, Long>{
#Query("SELECT u FROM User u where u.ldapId= ?1")
public User findUserByLdapId(String loginName);
}
Controller:
#Controller
#RestController
public class UserController {
#Autowired
private IUserService userService;
#CrossOrigin
#RequestMapping(value = "/dashboard", params = {"user"}, method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<User> getUser(#RequestParam(value = "user") String ldapId) {
User user = userService.findUserByLdapId(ldapId);
if(user == null)
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
return new ResponseEntity<>(user, HttpStatus.OK);
};
}
So a json would looks like:
{
"user": {
"userId": 1,
"fullName": "Carolina Ponce",
"roles":[]
}
}
Thanks in advance!
It seems you're asking for two different things: how to fetch #OneToMany association lazily (which should work out of the box) and how to make your JSON look like the above (which has nothing to do with how your JPA entity fetching is configured).
If you serialize your entity using Jackson, by default all the fields will get serialized, including those that are fetched lazily. If the persistence context is still open when you begin to serialize the entities, Jackson will simply trigger lazy loading by accessing the property. As a result, regardless of whether you use FetchType.EAGER or FetchType.LAZY, roles will be included in the result (I assume it is the case, since you'd be getting a LazyInitializationException if the context was closed).
The solution is, simply, to tell Jackson to refrain from serializing roles using #JsonIgnore if you don't want the property in the result.
First of all Hibernate and Spring Data are totally different tools designed for different purposes and they work with each other just fine. You can read more about the differences between both here: What Is the Difference Between Hibernate and Spring Data JPA?
Spring Data has nothing to do with how you fetch entity's related collections, Hibernate, as an implementation of JPA, does.
I assume you indeed fetch your collection lazily and that happens during serialization of your entity so the mechanism works as it should.
I suggest you to create some sort of DTO object that you could return from your controller rather than returning an entity. It seems like you don't want to expose user's roles so creating a DTO without roles may be a good idea. Other solution is to instruct Jackson to ommit roles property during serialization using #JsonIgnore annotation.

Design of DTO from Model for an API

I am designing an API using Spring boot for some social network backend. My current model looks like this:
public class User {
private long id;
private String handle;
private String name;
private List<User> followers;
private List<User> following;
// Getters setters etc
Now, I have created DTO which closely resembles above structure. My problem is that sometimes I want to return exactly what's above (which is fine) but sometimes, I don't want that.
For example when someone is only interested in finding followers of user, I don't want to include followers and following (I am simply interested in id, handle and name so computing followers and following for all of those users would be an incredible waste of resources).
In my current implementation those fields are returned with null values, which I don't think is a great idea. Should I create a separate DTO without those lists with just id, handle and name? Or is there more elegant way to do it?
It is a controversial issue. If you don't want to create separate dto there are several ways to do it. It depends on what data access approach you are going to use:
Using Spring Data JPA it is possible to return an entity in projection. You just need to add an additional constructor to your entity:
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select new User(u.id,u.name) from User u")
List<User> findAllUserItems();
}
Or same using JPA EntityManger:
public List<User> findAllUserItems() {
return entityManager.createQuery("select new User(u.id,u.name) from User u", User.class)
.getResultList();
}
If you wonder about unnecessary null fields and you are using Jackson, it is possible to configure to ignore null fields. For Spring Boot:
spring.jackson.default-property-inclusion=non_null
Or with Java config:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
return builder;
}
Or for not Spring Boot project:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
}
#Bean
public HttpMessageConverter converter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
Also, if you going to use Hibernate Session. To map directly to dto you may use AliasToBeanResultTransformer:
public List<UserDto> findAllUserItems() {
return session.createQuery("select u.id as id,u.name as name from User u")
.setResultTransformer(Transformers.aliasToBean(UserDto.class))
.list();
}
Suppose you can try like this.
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne
private User parent;
#OneToMany(mappedBy="parent")
private List<User> followers;
#OneToMany(mappedBy="parent")
private List<User> following;
// Getters, Setters etc....
}
Also this post may be helpful.

Using #JsonView with Spring Hateoas

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"

Conversion of DTO to entity and vice-versa

I am using Spring MVC architecture with JPA in my web application. Where to convert data transfer object (DTO) to JPA entity and vice-versa, manually (that is, without using any framework)?
This is an old question with accepted answer but thought to update it with easy way of doing it using model-mapper API.
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>0.7.4</version>
</dependency>
Using this API, you avoid manual setter & getters as explained in accepted answer.
In my opinion, both conversions should happen at controller with the help of private utility methods and using Java8 stream's map ( if a Collection of DTOs is exchanged ) like illustrated in this article.
It should happen at controller because DTOs are meant to be exclusive transfer objects. I don't take my DTOs further way down.
You code your service & data access layers on entities and convert DTOs to entities before calling service methods & convert entities to DTOs before returning response from controller.
I prefer this approach because entities rarely change and data can be added / removed from DTOs as desired.
Detailed model mapper configuration and rules are described here
I suggest another approach without extra dependency:
import org.springframework.beans.BeanUtils
...
BeanUtils.copyProperties(sourceObject, targetObject);
Can be used to convert DTO to entity, or vice-versa, if they have same property types and names.
If you want to ignore some fields, just add them after the targetObject.
BeanUtils.copyProperties(sourceObj, targetObj, "propertyToIgnoreA", "propertyToIgnoreB", "propertyToIgnoreC");
Source: http://appsdeveloperblog.com/dto-to-entity-and-entity-to-dto-conversion/
I think this is the cleanest way. Remember to check the Javadoc for caveats!
I can recommend to use mapstruct library:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.2.0.Final</version>
</dependency>
For example, if you have such an entity:
public class Entity {
private Integer status;
private String someString;
private Date startDate;
private Date endDate;
// SKIPPED
And DTO:
public class Dto {
private Boolean status;
private String someString;
private Long startDate;
private Long endDate;
// SKIPPED
Then the transformation can be done in the service layer by this way:
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
SomeDao someDao;
#Autowired
SomeMapper someMapper;
public Dto getSomething(SomeRequest request) throws SomeException {
return someDao.getSomething(request.getSomeData())
.map(SomeMapper::mapEntityToDto)
.orElseThrow(() -> new SomeException("..."));
}
Mapper can be represented as follows:
#Mapper
public interface SomeMapper {
#Mappings(
{#Mapping(target = "entity",
expression = "java(entity.getStatus() == 1 ? Boolean.TRUE : Boolean.FALSE)"),
#Mapping(target = "endDate", source = "endDate"),
#Mapping(target = "startDate", source = "startDate")
})
Dto mapEntityToDto(Entity entity);
}
I think you are asking about where to write whole entity-->DTO conversion logic.
Like Your entity
class StudentEntity {
int age ;
String name;
//getter
//setter
public StudentDTO _toConvertStudentDTO(){
StudentDTO dto = new StudentDTO();
//set dto values here from StudentEntity
return dto;
}
}
Your DTO Should be like
class StudentDTO {
int age ;
String name;
//getter
//setter
public StudentEntity _toConvertStudentEntity(){
StudentEntity entity = new StudentEntity();
//set entity values here from StudentDTO
return entity ;
}
}
And Your Controller should be like
#Controller
class MyController {
public String my(){
//Call the conversion method here like
StudentEntity entity = myDao.getStudent(1);
StudentDTO dto = entity._toConvertStudentDTO();
//As vice versa
}
}
In my opinion
the Entity -> DTO conversion should be done in the Controller before dispatching the jsp page
the DTO -> Entity conversion should be done in the Controller as well after validating the DTO returned from the jsp page
Its gives you more control over the process and you do not have to change the service/persistence classes every time some logic populating the Entity is changed.
Used mapstruct library. Additionally added the following in build.gradle
sourceSets {
main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main/"
}

Categories