在 Spring Boot 应用程序上保留 JPA(加上 Jackson)中的“计算”字段

Posted

技术标签:

【中文标题】在 Spring Boot 应用程序上保留 JPA(加上 Jackson)中的“计算”字段【英文标题】:Persist "computed" field in JPA (plus Jackson) on Spring Boot application 【发布时间】:2018-02-16 19:03:58 【问题描述】:

我有一个 JPA 实体,看起来像:

public final class Item implements Serializable 
  @Column(name = "col1")
  private String col1;

  @Column(name = "col2")
  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
  private String col2;

  @Column(name = "col3")
  private String col3;

  Item()  

  public Item(String col1, String col2) 
    this.col1 = col1;
    this.col2 = col2;
    col3 = col1 + col2 + "some other stuff";
  

  // getters, equals, hashCode and toString

我希望能够坚持col3。我正在通过 POST 发送一个请求,如下所示:


  "col1": "abc",
  "col2": "def"

...我收到这样的信息:

[
    
        "createdAt": "2017-09-07T19:18:17.04-04:00",
        "updatedAt": "2017-09-07T19:18:17.04-04:00",
        "id": "015e5ea3-0ad0-4703-af04-c0a3d46aa836",
        "col1": "abc",
        "col3": null
    
]

最终,col3 没有被持久化在数据库中。我没有任何二传手。

有什么办法可以做到吗?

更新

公认的解决方案是“侵入性较小”的解决方案。 Jarrod Roberson 提出的建议也完美无缺。最终,您可以通过在 col2 上使用 setter 并在那里设置 col3 的值来实现相同的效果——但我不喜欢这个……虽然是个人喜好。

【问题讨论】:

col3 这样的字段被称为计算字段是有原因的,您始终可以从其他字段“计算”它们。你能提供一个有效的用例吗?为什么你不能用一个简单的 getter 来解决这个问题?类似:public String getCol3() col1 + col2 + "some other stuff" 并删除 col3 属性。 @vl4d1m1r4 我一直都是这样,直到“需要将这个值持久化”也在数据库中。我之前在吸气剂中返回它。该字段实际上仅由 col2 组成:最后 3 个字符(并且它永远不会改变)。我实际上可以修改用户代理来发送它,但这没有任何意义,因为我已经有了整个数据,我可以拆分它并获取所需的部分。 【参考方案1】:

之所以不持久化,是因为您提供的带有col1col2 属性的构造函数从未真正被调用过。当spring从您发送到服务器的JSON进行映射(在jackson的帮助下)时,它使用默认构造函数来创建对象,然后调用setter(有时通过反射)来设置值。因此,col3 存储在数据库中的值始终为空。请参阅 Jarrod Roberson 的回答如何解决它:)。

【讨论】:

你说得对,我最初“倾向于”为col2 设置一个设置器(并在那里计算col3),因为这是我最初使它工作的唯一方法......但我喜欢Jarrod Roberson 回答得更好。感谢您的提示!【参考方案2】:

你要找的是@JsonCreator,它是这样使用的:

@JsonCreator()
public Item(@JsonProperty("col1") String col1, @JsonProperty("col2") String col2) 
  this.col1 = col1;
  this.col2 = col2;
  this.col3 = col1 + col2 + "some other stuff";

然后删除默认的无参数构造函数,Jackson 将使用这个构造函数,你想要的就会发生自动魔术

这是一个非常古老的 feature 来自 1.x 时代。您还可以注释 static 工厂方法 而是在需要的情况下创建构造函数private 使用更复杂的逻辑,比如用 a 构造的东西 建造者模式

这是一个例子:

@JsonCreator()
public static Item construct(@JsonProperty("col1") String col1, @JsonProperty("col2") String col2) 
  return new Item(col1, col2, col1 + col2 + "some other stuff");


private Item(final String col1, final String col2, final String col3) 
  this.col1 = col1;
  this.col2 = col2;
  this.col3 = col3;

【讨论】:

这很酷。我以前使用过@JsonCreator,但我没有想到在这里使用。一件事:我必须保留默认构造函数;看起来系统已经使用这个实体的方式(它实际上更复杂,因为它在一个集合中并且也包装在一个不同的对象中)。不过不确定。感谢您的提示! 我认为 JPA/Hibernate 是导致您必须保留默认构造函数的原因。 JPA/Hibernate 正在削弱实际的中/大型项目。啊!【参考方案3】:

虽然有一个公认的答案,但它似乎过于复杂,需要删除违反 JPA 规范的默认构造函数。

第 2.1 节

实体类必须有一个无参数的构造函数。实体类可以 还有其他构造函数。无参数构造函数必须是公共的 或受保护。

没有必要让杰克逊参与进来。您可以简单地使用 JPA 预持久侦听器来确保在刷新操作之前设置 col3

https://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html

public final class Item implements Serializable 
  @Column(name = "col1")
  private String col1;

  @Column(name = "col2")
  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
  private String col2;

  @Column(name = "col3")
  private String col3;

  Item()  

  @PrePersist
  public void updateCol3()
      col3 = col1 + col2;
  

【讨论】:

我还需要检查这种方法。我认为它不起作用的唯一原因是因为这个类的层次结构很长。 “基”类中已经存在一个@PrePersist,尽管先调用super,然后这个可能会起作用。这是可能的,但我不知道这是否会产生“副作用”。稍后当我能够测试它时会更新它。感谢您的信息! JPA 监听器将在类层次结构中以一致的顺序执行:***.com/a/26063003/1356423

以上是关于在 Spring Boot 应用程序上保留 JPA(加上 Jackson)中的“计算”字段的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot 应用程序上添加 jpa 依赖项时,Okta Spring Boot 不起作用

Spring Boot REST JPA JSON 格式

关于如何使用 jpa-repositiroies 在 Spring-Boot 上持久保存数据的指南/教程

如何在 Spring Boot 应用程序的同一个域类上同时使用 Spring Data JPA 和 Spring Data Elasticsearch 存储库?

Spring Boot 应用程序不会为 JPA @Table 注释创建模式

如何禁用嵌入式数据库 Spring-boot spring-data-jpa