Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring REST Hibernate Application Design

Environment :

Spring 4 REST

Spring MVC

Hibernate

Issue :

We are developing an application with below stack.

enter image description here

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 :

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

  2. 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??

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

  2. 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?

like image 524
Atul Avatar asked Dec 25 '15 08:12

Atul


3 Answers

I strongly recommend to use DTOs level, here are several reasons:

  1. At some point your REST representation will not be matched completely to DAO Entity. Here are few examples:

    • You need to return full list of lightweight user info (only user first and last name) for mobile version of your app
    • You want to provide User info loaded from DAO + some payment account info, retrieved from separate service.
    • You want to combine information from two separate DAO entities into one service call
    • etc.
  2. 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.

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

like image 159
udalmik Avatar answered Nov 09 '22 20:11

udalmik


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);
   }


}
like image 32
Rafik BELDI Avatar answered Nov 09 '22 19:11

Rafik BELDI


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

like image 33
R K Punjal Avatar answered Nov 09 '22 20:11

R K Punjal