簡體   English   中英

Spring REST Hibernate應用程序設計

[英]Spring REST Hibernate Application Design

環境 :

Spring 4 REST

Spring MVC

過冬

問題 :

我們正在開發一個低於堆棧的應用程序。

在此輸入圖像描述

Spring REST Web服務將為客戶端公開API,它將在UI(ASP .NET)上顯示它。 響應以JSON格式發送。

考慮以下情況:

客戶端調用REST api以獲取具有ID的用戶。 dao層提取用戶實體,並將被提交給客戶端。

以下場景的問題/觀察:

  1. 由於用戶可以使用與Hibernate映射相關的其他實體(例如userRoles使用oneToMany),因此還需要獲取這些實體,否則拋出LazyInitialization異常,因為UI嘗試通過User對象訪問這些集合。

  2. 並非響應中需要User對象中的所有屬性(例如:某些請求不需要用戶擁有的角色)。

考慮到上面的圖片,通過Spring REST向客戶端發送User對象(或響應)的最佳設計方法是什么?

  1. 創建一個模擬實體對象的中間對象層(如DTO)。 根據要求在服務層中填充此DTO。 由於服務層在交易中運行,因此將解決第1號問題。 但這需要在實體和DTO之間進行額外的復制

  2. 在Hibernate實體/查詢級別處理問題編號1/2(連接獲取查詢或修改映射)並通過注釋排除響應中不需要的屬性,如:@JsonIgnore。 但是這種方法不靈活,需要非常仔細地設計實體類

任何人都可以對此發表評論嗎? 還有更好的選擇嗎?

我強烈建議使用DTO級別,原因如下:

  1. 在某些時候,您的REST表示將不會完全匹配DAO實體。 以下是一些例子:

    • 您需要為應用的移動版本返回輕量級用戶信息的完整列表(僅限用戶名和姓)
    • 您想提供從DAO +加載的用戶信息以及從單獨服務中檢索到的某些付款帳戶信息。
    • 您希望將來自兩個單獨DAO實體的信息組合到一個服務調用中
    • 等等
  2. 使用某些第三方庫(EhCache,Hazelcast等)或簡單的Map類結構緩存數據 - Hibernate實體的自定義序列化可能會成為具有復雜關系的實體的巨大痛苦。

  3. 使用DTO級別,您可以將服務接口/ DTO作為接口/客戶端庫,以便與其他組件集成。 您仍然可以安全地修改/完全重新設計DAO層實現,甚至可以切換到No SQL解決方案。

作為結論 - 在REST API中使用Hibernate實體可以很好地處理簡單的“Hello World”應用程序,並且不適用於大多數現實生活中的解決方案。

選項1是最好的方法

創建一個模擬實體對象的中間對象層(如DTO)

創建DTO對象將使您的設計更加靈活。您所要做的就是處理其余Controller中的DTO對象而不是服務層,這樣您就可以使用相同的服務層來生成許多DTO。

在實體和DTO之間復制,這是一項額外的工作,但您可以使用Mapper來處理Dozer

考慮這個例子:

@Service
public class MyService {

  @Transactional 
  public User getUserBId(Long id){
   User user = ....
   return user;
  }

}

休息控制器:

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


}

在這樣的scenariou你理想情況下你應該使用Hibernate4Module(參考Hibernate4Module github鏈接

使用此選項將確保在spring rest層上將實體序列化為JSON,並且不會嘗試訪問它們(或者在您的情況下將它們序列化為JSON)。

我將在下面列出可能的解決方案代碼。

如果您使用maven,這些將是您的依賴項:

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

創建一個類HibernateAwareObjectMapper並在其中注冊Hibernate4Module。

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

如果您使用的是基於spring bean xml的配置,則可以使用以下內容:

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

否則如果你在Spring啟動時使用基於純Java的配置,你可以這樣做:

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

}

然后您的控制器看起來像這樣:

@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

}

此外,如果您仍希望傳遞實體的延遲加載屬性,則在您的服務/ DAO層或由@Transactional注釋的層中

你可以這樣做:

Hibernate.initialize(myEntity.getLazyLoadedAttribute());

我希望這有幫助 :)

如果我的回答可以幫助你,請做upvote並將其標記為答案:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM