Spring REST Hibernate 应用程序设计

7

环境 :

Spring 4 REST

Spring MVC

Hibernate

问题 :

我们正在开发一个应用程序,使用以下技术栈:

enter image description here

Spring REST Web服务将公开API,供客户端使用并在UI(ASP .NET)中显示。响应以JSON格式发送。

考虑以下情况:

客户端调用REST API获取ID为User的用户。dao层获取User实体并将其传递给客户端。

对于上述场景,存在以下问题/观察:

  1. 由于用户可以通过Hibernate映射(例如使用oneToMany的userRoles)与其他实体相关联,因此还需要获取这些实体,否则当UI通过User对象访问这些集合时,将抛出LazyInitialization异常。

  2. 并非所有User对象中的属性都需要在响应中返回(例如:某些请求不需要用户拥有的角色)。

考虑上述情况,请问将User对象(或响应)通过Spring REST发送到客户端的最佳设计方法是什么?

  1. 创建一个中间层对象(例如DTO),模仿实体对象。在Service层根据需要填充此DTO。由于服务层在事务内运行,因此将解决问题1。但是,这需要在实体和DTO之间进行额外的复制。

  2. 在Hibernate实体/查询级别(使用join fetch查询或重新设计映射)或通过注释(如:@JsonIgnore)排除响应中不需要的属性来处理问题1/2。但是,这种方法不灵活,并且需要非常谨慎地设计实体类。

请问是否有更好的替代方案?


1
我更喜欢使用DTO,尽管需要使用Mapper或Adapter来映射实体和DTO之间的关系。使用DTO有其优缺点,详情请参见链接。总的来说,使用DTO提供了表示层和领域层之间的松耦合,但需要维护DTO/DTO映射层。 - Madhusudana Reddy Sunnapu
3个回答

5

我强烈建议使用DTO层,以下是几个原因:

  1. 在某些时候,你的REST表示形式可能无法完全匹配DAO实体。以下是一些例子:

    • 你需要为应用程序的移动版本返回完整的轻量级用户信息(仅用户的名字和姓氏)
    • 你想要提供从DAO加载的用户信息+从单独的服务检索的一些付款帐户信息。
    • 你想要将来自两个不同的DAO实体的信息组合成一个服务调用
    • 等等。
  2. 使用第三方库(EhCache、Hazelcast等)或类似Map结构的数据缓存 - 对于具有复杂关系的实体进行自定义Hibernate实体序列化可能会变得非常棘手。

  3. 使用DTO层,你有服务接口/ DTOs作为与其他组件集成的接口 /客户端库。并且即使完全重新设计您的DAO层实现,甚至切换到NoSQL解决方案,您仍然可以安全地修改。

总之 - 在REST API中使用Hibernate Entities适用于简单的“Hello World”应用程序,但对于大多数实际生活解决方案而言并不适用。


2

方案一是最佳方法

创建一个中间层对象(如DTO),模仿实体对象

创建DTO对象将使您的设计更加灵活,您只需要在rest控制器中处理DTO对象而不是在服务层中处理它们,这样您就可以使用相同的服务层来生成许多DTO。

在实体和DTO之间进行复制是额外的工作,但您可以使用Mapper来处理,例如Dozer

考虑以下示例:

@Service
public class MyService {

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

}

Rest控制器:

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


}

1
在这种情况下,您应该理想地使用Hibernate4Module(参考Hibernate4Module github Link)。
使用它将确保将实体序列化为JSON时,在spring rest层上尊重懒加载属性,并且不会尝试访问它们(或在您的情况下将其序列化为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());
    }
}

如果您正在使用基于XML的Spring Beans配置,您可以使用以下类似的内容:
<?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 Boot的纯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());

我希望这可以帮助你 :)
如果我的回答对你有帮助,请点赞并将其标记为答案 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接