Environment :
Spring 4 REST
Spring MVC
Hibernate
Issue :
We are developing an application with below stack.
The Spring REST web service will expose APIs for client which will display it on UI (ASP .NET ) . The response is sent in JSON.
Consider below scenario :
Client calls REST api to get User with ID. The dao layer fetches User entity and will be delievred to client.
And below issues/observations for above scenario :
Since User can have another entities related with it throgh Hibernate mapping (like userRoles using oneToMany), these entities also need to be fetched, else LazyInitialization exception is thrown since UI tries to access these collections through User object.
Not all properties in User object will be required in response (e.g: some requests won't need roles a user have).
Considering above picture in mind , what is the best design approach to send User object (or response) to client through Spring REST??
Create an intermediate layer of objects (like DTOs) mimicking entity objects. Have this DTOs populated in Service layer as per requirement. Since service layer runs inside transaction issue number 1 will be resolved. But this requires extra copying between entity and DTOs
Handle issue number 1/2 at Hibernate entity / query level (join fetch queries or revamping mapping) and exclude properties not required in response through annotations like: @JsonIgnore. But this approach is not flexible and requires very careful design of entity classes
Can anybody please comment on this? Is there any better alternative available?
I strongly recommend to use DTOs level, here are several reasons:
At some point your REST representation will not be matched completely to DAO Entity. Here are few examples:
Caching of data using some 3rd party library (EhCache, Hazelcast, etc.) or simple Map like structure - Custom serialization of Hibernate entities could become a big pain for entities with complex relationships.
With DTO level you have Service Interfaces/DTOs as an interface/client library for integration with other components. And you still safe to modify/completely redesign your DAO layer implementation, even switching to No SQL solution for example.
As a conclusion - using Hibernate Entities in REST API works fine for simple "Hello World" like apps and doesn't work for most of real life solutions.
Option 1 is the best approach
Create an intermediate layer of objects (like DTOs) mimicking entity objects
Creating DTO object will make your design more flexible, All you have to do is to handle the DTO objects within the rest Controller not in the service Layer, that way you can use the same service Layer to produce many DTO's.
Copying between entity and DTOs, it is an extra work, but you can use a Mapper
to handle that like Dozer
Consider this example :
@Service
public class MyService {
@Transactional
public User getUserBId(Long id){
User user = ....
return user;
}
}
Rest Controller:
@RestController
public UserRestController {
@Resource
private Myservice service;
@Resource
private Mapper mapper;
// here you can use a dto
@RequestMapping(...)
public UserDto getUser(@RequestParam()Long userId){
User user = service.getUserBId(userId);
return mapper.map(user,UserDto.class);
}
}
In such a scenariou you should ideally be using Hibernate4Module (reference Hibernate4Module github Link)
Using this will ensure the serialization of entities to JSON on the spring rest layer respects the lazy loaded attributes and will not try access them (or serialize them to JSON in your case).
I will list out a possible solution code below.
If you are using maven, these would be your dependencies :
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>2.3.0</version>
</dependency>
Create a class HibernateAwareObjectMapper and register the Hibernate4Module in it.
package com.mypackage.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
public class HibernateAwareObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 1L;
public HibernateAwareObjectMapper() {
registerModule(new Hibernate4Module());
}
}
If you are using spring beans xml based config you could use something like below :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="...">
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.mypackage.web.HibernateAwareObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
else in case you are using pure java based config as in Spring boot you can do it like this:
@Configuration
@EnableWebMvc
@EnableAsync
@ComponentScan(basePackages = { "com.mypackage.controller" }) // package referring to controllers
@PropertySource("classpath:imagesConfig.properties")
public class WebConfiguration
extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter
{
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters){
List<MediaType> supportedMediaTypes=new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(MediaType.TEXT_PLAIN);
MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(new HibernateAwareObjectMapper());
converter.setPrettyPrint(true);
converter.setSupportedMediaTypes(supportedMediaTypes);
converters.add(converter);
super.configureMessageConverters(converters);
}
}
Then your controllers could look something like this:
@RequestMapping(value="/doSomething", method=RequestMethod.POST, produces="application/json;charset=UTF-8")
public @ResponseBody MyCustomWebResponseObject<MyEntity> create(@Valid @RequestBody MyEntity myEntity) throws Exception {
// do whatever
}
Also Now if you want to still pass along a lazy loaded attribute of an entity, then in your service/DAO layer or the layer which is annotated by @Transactional
you could do this :
Hibernate.initialize(myEntity.getLazyLoadedAttribute());
I hope this helps :)
do upvote and mark this as an answer if my answer helps you :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With