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