使用 Spring Boot 和 Jackson 避免两个不同的域模型

Posted

技术标签:

【中文标题】使用 Spring Boot 和 Jackson 避免两个不同的域模型【英文标题】:Avoiding two distinct domain models with Spring Boot and Jackson 【发布时间】:2017-09-02 01:07:29 【问题描述】:

我正在设计一个由 mysql 支持的 Spring Boot REST API。我突然想到,我想要为我的所有域对象创建两个独立的模型:

模型 1:在外部世界(REST 客户端)和我的 Spring REST 控制器之间使用;和 模型 2:Spring Boot 应用和 MySQL 数据库之间内部使用的实体

例如,我可能有一个 contacts 表用于保存个人/联系信息:

CREATE TABLE contacts (
  contact_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  contact_ref_id VARCHAR(36) NOT NULL,
  contact_first_name VARCHAR(100) NOT NULL,
  ...many more fields
);

它对应的 Spring/JPA/Hibernate 实体可能如下所示:

// Groovy pseudo-code!
@Entity
class Contact 
  @Id
  @Column(name = "contact_id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  Long id

  @Column(name = "contact_ref_id")
  UUID refId

  @Column(name = "contact_first_name")
  String firstName

  // ...etc.

如果我只有一个模型范例,那么当 Jackson 将 Contact 实例(可能从数据库中取回)序列化为 JSON 并将其发送回客户端时,他们会看到如下所示的 JSON:


  "id" : 45,
  "refId" : "067e6162-3b6f-4ae2-a171-2470b63dff00",
  "firstName" : "smeeb",
  ...

没有什么比将主键暴露给外界!相反,我希望序列化的 JSON 省略 id 字段(以及其他字段)。另一个例子可能是像Colors这样的查找/参考表:

# Perhaps has 7 different color records for ROYGBIV
CREATE TABLE colors (
  color_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  color_name VARCHAR(20) NOT NULL,
  color_label VARCHAR(20) NOT NULL,
  color_hexcode VARCHAR(20) NOT NULL,

  # other stuff here
);

如果对应的Color 实体如下所示:

@Entity
class Color 
  @Id
  @Column(name = "color_id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  Long id

  @Column(name = "color_name")
  String name

  @Column(name = "color_label")
  String label

  @Column(name = "color_hexcode")
  String hexcode

  // ...etc.

然后只有一个模型,它会像这样序列化为 JSON:


  "id" : 958,
  "name" : "Red",
  "label" : "RED",
  "hexcode" : "ff0000"

但也许我只是希望它作为一个简单的字符串值返回:


  "color" : "RED"

所以在我看来,我要么需要两个单独的模型(以及在它们之间映射的映射器类),要么需要一种方法来注释我的实体或配置 Spring、Jackson 甚至 Hibernate 以在我的实体上应用某些转换在正确的时间。这些框架是否提供了任何可以帮助我的东西,还是我必须在这里使用两个不同的领域模型?

【问题讨论】:

虽然可以使用注释和 Mixins 来控制 Jackson 序列化。我总是有不同的 DTO 和实体类,它们的使用方式只是不同的要求。如果您不这样做,您将不得不处理诸如 Hibernate 延迟加载集合之类的无法序列化的事情,因为在您的控制器方法退出后,persistenceContext 已关闭。如果您使用 Lombok,创建 DTO 需要 1 分钟,并且您可以在 Entity 上拥有任意数量的 DTO(视图)。 【参考方案1】:

您实际上可以只使用一个模型来完成此操作,如果您只是在寻找隐藏字段、自定义格式、属性的简单转换等,我认为这是最简单的方法。拥有两个模型需要从一个模型转换到另一个模型,反之亦然- 反之亦然,这是一种痛苦。 Jackson 提供了许多有用的注释,可用于自定义输出。下面列出了一些可能对您有用的注释

@JsonIgnore - 忽略字段/属性。您可以使用此注释隐藏您的 id 字段。

@JsonInclude - 可用于指定字段何时应出现在输出中。例如:如果一个字段为空,是否应该出现在输出中

@JsonSerialize - 您可以为属性指定自定义序列化程序。例如:您有一个属性“密码”,并且您想将密码输出为“****”。

@JsonFormat - 您可以将自定义格式应用于字段。如果您有日期/时间字段,这将非常有用

@JsonProperty - 如果您想为输出中的字段指定不同的名称。例如:您的模型中有一个字段“名称”,您希望在输出中将其显示为“用户名”。

【讨论】:

以上是关于使用 Spring Boot 和 Jackson 避免两个不同的域模型的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Boot 和 Jackson 的日期时区

Spring Boot Jackson 和数据序列化

使用 Spring Boot 和 Jackson 避免两个不同的域模型

使用 Spring Boot、Jackson 和 Hibernate 的多对多关系

Spring Boot 不使用配置的 Jackson ObjectMapper 和 @EnableWebMvc

Spring Boot没有使用Jackson Kotlin插件