Roman
Roman

Reputation: 2003

How to automatically serialize and deserialize JSON string using JPA and Hibernate?

I have data class/table "User" that has column "preferences"

CREATE table "user"; 
ALTER TABLE "user" ADD COLUMN preferences TEXT;

Preferences type is TEXT and I am storing JSON there.

public class User extends AbstractEntity{
public String preferences;
}

so user.preferences value is "{notifyByEmail:1, favouriteColor:"blue" }"

How can I wrap it with some annotation so I can access it like

user.preferences.notifyByEmail

or without need to wrap into data object

user.preferences.get("notifByEmail");
user.preferences.set("notifByEmail",true);

I imagine there could be some Jackson annotation that I can add to field like

@JsonGenerate
public String preferences;

I am fairly new to JPA and documentation is steep.

I believe my case is quite common. Can anyone give any examples?

Upvotes: 9

Views: 26458

Answers (4)

Vlad Mihalcea
Vlad Mihalcea

Reputation: 153780

It's very easy to persist JSON objects using Hibernate.

You don’t have to create all these types manually, you can simply get them via Maven Central using the following dependency:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-62</artifactId>
    <version>${hibernate-types.version}</version> 
</dependency> 

For more info, check out the Hibernate Types open-source project.

For Hibernate 6

The entity mapping will look like this:

@Type(JsonType.class)
@Column(columnDefinition = "jsonb")
private String preferences;

For Hibernate 5

You need to add this @TypeDef annotation on the class level or in a package-info.java package-level descriptor to use the JsonType Hibernate Type:

@TypeDef(
    name = "json",
    typeClass = JsonType.class
)

Afterward, the entity mapping will look like this:

@Type(type = "json")
@Column(columnDefinition = "jsonb")
private String preferences;

That's it!

Upvotes: 4

Kumar
Kumar

Reputation: 907

Can achieve this using JPA Converter.

Entity;

@Id
@GeneratedValue
Long id;

@Column(name = "mapvalue")
@Convert(converter = MapToStringConverter.class)
Map<String, String> mapValue;

Converter:

@Converter
public class MapToStringConverter implements AttributeConverter<Map<String, String>, String> {

    ObjectMapper mapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(Map<String, String> data) {
        String value = "";
        try {
            value = mapper.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return value;
    }

    @Override
    public Map<String, String> convertToEntityAttribute(String data) {
        Map<String, String> mapValue = new HashMap<String, String>();
        TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {
        };
        try {
            mapValue = mapper.readValue(data, typeRef);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return mapValue;
    }

}

Saving data :

Map<String, String> mapValue = new HashMap<String, String>();
mapValue.put("1", "one");
mapValue.put("2", "two");
DataEntity entity = new DataEntity();
entity.setMapValue(mapValue);
repo.save(entity);

The value will store in DB as

{"1":"three","2":"two"}

Upvotes: 18

Luis Vargas
Luis Vargas

Reputation: 2524

If you really need something like that the most recommendable is to do the next:

public class User extends AbstractEntity{
  @JsonIgnore //This variable is going to be ignored whenever you send data to a client(ie. web browser)
  private String preferences;

  @Transient //This property is going to be ignored whenever you send data to the database
  @JsonProperty("preferences") //Whenever this property is serialized to the client, it is going to be named "perferences" instead "preferencesObj"
  private Preferences preferencesObj;

  public String getPreferences() {
    return new ObjectMapper().writeValueAsString(preferencesObj);
  }

  pbulic void setPreferneces(String preferences) {
    this.preferences = preferences;
    this.preferncesObj = new ObjectMapper().readValue(preferences, Preferences.class);
  }

  pubilc Preferences getPreferencesObj() {
    return preferencesObj;
  }

  public void setPreferencesObj(Preferences preferencesObj) {
    this.preferencesObj = preferencesObj;
  }
}

Additional Notes:

  • Maybe the private property "preferences" could be deleted and only use getter and setter.
  • I haven't test that code.
  • The above code is intended to use Jackson ObjectMapper and Hibernate module.

Upvotes: 2

siebz0r
siebz0r

Reputation: 20339

Honestly I think your best solution is to create a separate table (preference) for your properties.

+------------+
| preference |
+------------+---------+------+-----+
| Field      | Type    | Null | Key |
+------------+---------+------+-----+
| user_id    | bigint  | NO   | PRI |
| key        | varchar | NO   | PRI |
| value      | varchar | NO   |     |
+------------+---------+------+-----+

You can map this in your entity like this:

@Entity
public class User
{
    @Id
    private Long id;

    @ElementCollection
    @MapKeyColumn(name = "key")
    @Column(name = "value")
    @CollectionTable(name = "preference",
        joinColumns = @JoinColumn(name = "user_id"))
    private Map<String, String> preferences;
}

This way your database is more normalized and you don't have to fool around with 'creative solutions' like storing preferences as JSON.

Upvotes: 6

Related Questions