如何使用 JPA 和 Hibernate 自动序列化和反序列化 JSON 字符串?

Posted

技术标签:

【中文标题】如何使用 JPA 和 Hibernate 自动序列化和反序列化 JSON 字符串?【英文标题】:How to automatically serialize and deserialize JSON string using JPA and Hibernate? 【发布时间】:2012-08-14 08:55:18 【问题描述】:

我有包含“首选项”列的数据类/表“用户”

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

首选项类型是 TEXT,我在其中存储 JSON。

public class User extends AbstractEntity
public String preferences;

所以user.preferences 的值为"notifyByEmail:1, favouriteColor:"blue" "

我怎样才能用一些注释来包装它,以便我可以像访问它一样访问它

user.preferences.notifyByEmail

或者不需要包装到数据对象中

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

我想可能有一些 Jackson 注释可以添加到字段中 喜欢

@JsonGenerate
public String preferences;

我对 JPA 还很陌生,而且文档很繁琐。

我相信我的情况很常见。谁能举个例子?

【问题讨论】:

希望将此数据存储为 JSON 字符串而不是单独的不同字段(此处为布尔和枚举类型)的理由是什么? 理由是我不想将每个键值偏好都支持为单独的字段。偏好键:值正在增长和挂起,其中有很多。为它们中的每一个更改模型或将字段添加到数据库中都是多余的。 您是否考虑过使用属性表来记录值?用例确实很常见,但您提出的解决方案不太适合关系模型。 【参考方案1】:

可以使用 JPA 转换器实现这一点。

实体;

@Id
@GeneratedValue
Long id;

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

转换器:

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


保存数据:

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

该值将存储在数据库中

"1":"three","2":"two"

【讨论】:

【参考方案2】:

老实说,我认为您最好的解决方案是为您的属性创建一个单独的表(首选项)。

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

您可以像这样将其映射到您的实体中:

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

通过这种方式,您的数据库更加规范化,您不必胡乱使用“创造性解决方案”,例如将首选项存储为 JSON。

【讨论】:

感谢您提供全面的描述。我会试试的! 这与问题要求的方式不同。 @HiramChirino 我认为为提问者提供替代方案没有任何问题。特别是当替代方案可能提供更好的解决方案时。提问者似乎对这个解决方案感到满意(有帮助),答案并不轻松或不正确,所以我看不出有任何理由投反对票。为了 ***,请see the help pages on when to vote down。 这就是这样做的。我阅读这些映射注释如何使用的方式总是让我相信这种类型的键值存储在 JPA 下是不可能的。谢谢。【参考方案3】:

使用 Hibernate 持久化 JSON 对象非常容易。

你不必手动创建所有这些类型,你可以简单地得到 他们通过 Maven Central 使用以下依赖项:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>$hibernate-types.version</version> 
</dependency> 

欲了解更多信息,请查看Hibernate Types open-source project。

现在,您需要在类级别或 package-info.java 包级别描述符中添加此 @TypeDef 注释以使用 JsonType Hibernate 类型:

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

实体映射将如下所示:

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

就是这样!

【讨论】:

【参考方案4】:

如果你真的需要类似的东西,最推荐的是做下一个:

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;
  

补充说明:

也许私有属性“preferences”可以被删除并且只使用getter 和setter。 我没有测试那个代码。 以上代码旨在使用 Jackson ObjectMapper 和 Hibernate 模块。

【讨论】:

能否请您提供 JSON 数组的示例。我一直在努力用注释反序列化 JSON 数组。 这是否不需要一个名为 Preferences 的类,它需要 User 可以拥有的所有不同偏好? 是的。如果您喜欢动态的东西,最好使用地图。

以上是关于如何使用 JPA 和 Hibernate 自动序列化和反序列化 JSON 字符串?的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate JPA 序列(非 Id)

为啥在 JPA 2/Hibernate 中使用共享主键时实体需要可序列化?

如何设置在 Java JPA/Hibernate 中不是自动生成的 @Id 主键?

当为 Postgres DB 的属性 spring.jpa.hibernate.ddl-auto 提供更新值时,Hibernate 不会生成序列

如何在JPA / JAVA / Hibernate的两列中插入自动生成的ID

hibernate JPA:可选值生成