简体   繁体   中英

Persist “computed” field in JPA (plus Jackson) on Spring Boot application

I have a JPA entity that looks like:

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
}

I would like to be able to persist col3 . I'm sending a request via POST that looks like:

{
  "col1": "abc",
  "col2": "def"
}

...and I'm receiving something like this:

[
    {
        "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
    }
]

Eventually, col3 is not being persisted in the database. I don't have any setters.

Is there any way to achieve this?

UPDATE

The accepted solution is the "less intrusive" one. The proposed one from Jarrod Roberson also works flawlessly. And eventually, you can achieve the same by using a setter on col2 and set the value of col3 there — but I dislike this one...although a personal preference.

The reason why this is not persisted is because the constructor you have provided with col1 and col2 properties is never really called. When spring does the mapping (with help of jackson) from the JSON you send to the server it uses the default constructor to create the object and then calls the setters(sometimes through reflection) to set the values. Therefore the value that is stored in the db for col3 is always null. See Jarrod Roberson's answer how to solve it :).

What you are looking for is @JsonCreator it is used like this:

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

Then remove the default no-args constructor and Jackson will use this one and what you want will happen auto-magically .

This is a very old feature back from the 1.x era. You can also annotate a static Factory Method instead and make the constructor private for cases where you need to use more complicated logic like something that is constructed with a Builder Pattern .

Here is an example:

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

While there is an accepted answer it seems overly complex and requires the removal of the default constructor which is in violation of the JPA spec.

Section 2.1

The entity class must have a no-arg constructor. The entity class may have other constructors as well. The no-arg constructor must be public or protected.

There is no need to get Jackson involved here. You can simply use a JPA pre-persist listener to ensure col3 is set before the flush operation

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM