如何使用JPA将JSON列映射为Java对象

56

我们有一张列很多的大表格。在我们迁移到 MySQL 集群后,由于以下原因,这张表无法被创建:

ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 14000. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs(行大小超出最大值限制。对于所使用的表类型而言,不包括 BLOB 类型,最大行大小为 14000。这包括存储开销,请查看手册。您需要将一些列更改为 TEXT 或 BLOB 类型)

例如:

@Entity @Table (name = "appconfigs", schema = "myproject")
public class AppConfig implements Serializable
{
    @Id @Column (name = "id", nullable = false)
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private int id;

    @OneToOne @JoinColumn (name = "app_id")
    private App app;

    @Column(name = "param_a")
    private ParamA parama;

    @Column(name = "param_b")
    private ParamB paramb;
}

这是一个用于存储配置参数的表格。我在考虑将一些列合并为一个JSON对象,并将其转换为Java对象。

例如:

@Entity @Table (name = "appconfigs", schema = "myproject")
public class AppConfig implements Serializable
{
    @Id @Column (name = "id", nullable = false)
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private int id;

    @OneToOne @JoinColumn (name = "app_id")
    private App app;

    @Column(name = "params")
    //How to specify that this should be mapped to JSON object?
    private Params params;
}

我们定义了以下内容:

public class Params implements Serializable
{
    private ParamA parama;
    private ParamB paramb;
}

通过使用这个方法,我们可以将所有列合并成一个表格,或者将整个表格拆分为几个表格。个人而言,我更喜欢第一种解决方案。
无论如何,我的问题是如何映射参数列,它是文本,并包含 Java 对象的 JSON 字符串?

如果您有许多配置参数,只需使用两列的普通表格:键和值,并将其加载到映射中。如果您想将参数存储为JSON或XML,则只需将其存储/读取为文本,稍后进行转换即可。 - user1516873
@Rad,这个链接对你有帮助吗? - Ankur Singhal
@user1516873,我们认为这是最终解决方案。如果我没记错的话,在尝试修改数据时会增加复杂度。无论如何,还是谢谢。 - Rad
@ankur-singhal,我不确定。我们正在使用MySQL集群和NDBCluster引擎来管理我们的表格。这仍然适用吗? - Rad
值得查看的链接:https://dev59.com/jrjoa4cB1Zd3GeqPGudD#59942878 - Smile
9个回答

96

您可以使用JPA转换器将实体映射到数据库。只需在参数字段中添加类似于此注释:

@Convert(converter = JpaConverterJson.class)

然后以类似的方式创建类(这将转换为通用对象,您可能需要对其进行专门化):

@Converter(autoApply = true)
public class JpaConverterJson implements AttributeConverter<Object, String> {

  private final static ObjectMapper objectMapper = new ObjectMapper();

  @Override
  public String convertToDatabaseColumn(Object meta) {
    try {
      return objectMapper.writeValueAsString(meta);
    } catch (JsonProcessingException ex) {
      return null;
      // or throw an error
    }
  }

  @Override
  public Object convertToEntityAttribute(String dbData) {
    try {
      return objectMapper.readValue(dbData, Object.class);
    } catch (IOException ex) {
      // logger.error("Unexpected IOEx decoding json from database: " + dbData);
      return null;
    }
  }

}

就是这样:您可以使用这个类将任何对象序列化为表格中的JSON。


1
注意:如果您同时使用Hibernate Envers和低于5的Hibernate版本(在撰写本文时还未发布),则此解决方案不可行。请参阅HHH-9042 - Flavin
2
如果存储在数据库中的 JSON 是一个数组,类似于:[{...},{...},{...}],转换器会抛出异常还是映射器会处理它? - Norbert Bicsi
3
感觉应该有一个注释可以为我们完成这个操作。这是一个非常通用的用例,似乎很多人都需要。可惜我没有找到一个。谢谢你的回答。最终我自己解决了这个问题,在寻找更好的方法时,我偶然发现了你的答案,它本质上与我的相同。这只是让我更加确信我的方法是“最干净的”,不幸的是。 - Stevers
4
请勿将@Converter(autoApply = true)直接放在类上,因为这样会自动将此转换器应用于所有对象,而在序列化和反序列化时可能会出现日期类型和datetime SQL类型的问题。 - Mangesh Bhapkar
2
JPA将以LinkedHashMap的形式返回结果,因为对象类会将来自jso-db的每个Json属性转换为LinkedHashMap - Ravi Parekh
显示剩余5条评论

26

JPA的AttributeConverter在映射JSON对象类型方面受限,特别是如果您想将它们保存为JSON二进制类型时。您无需创建自定义Hibernate类型即可获得JSON支持,只需使用Hibernate Types OSS项目

例如,如果您使用Hibernate 5.2或更新版本,则需要在Maven pom.xml配置文件中添加以下依赖项:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version> 
</dependency> 
现在,您需要在实体属性级别或更好的是在基类中使用 @MappedSuperclass 在类级别声明新类型:
@TypeDef(name = "json", typeClass = JsonType.class)

实体映射将如下所示:

@Type(type = "json")
@Column(columnDefinition = "json")
private Location location;
如果您正在使用Hibernate 5.2或更高版本,则MySQL57Dialect 会自动注册 JSON 类型。

否则,您需要自己注册:
public class MySQLJsonDialect extends MySQL55Dialect {

    public MySQLJsonDialect() {
        super();
        this.registerColumnType(Types.JAVA_OBJECT, "json");
    }
}

然后,将Hibernate属性hibernate.dialect设置为刚刚创建的MySQLJsonDialect类的完全限定类名。


你能否请看一下这个问题 - MJBZA

15

如果需要在响应客户端(例如rest API响应)时将json类型的属性映射到json格式,请添加以下内容:@JsonRawValue

@Column(name = "params", columnDefinition = "json")
@JsonRawValue
private String params;

这可能不会为服务器端使用进行DTO映射,但客户端将以json格式正确格式化属性。


你需要同时使用 columnDefinition = "json"@JsonRawValue 注解吗? - B378
它在获取数据方面非常出色,但我如何发送新数据? - Shoniisra

10

这很简单。

@Column(name = "json_input", columnDefinition = "json")
private String field;

在MySQL数据库中,您的列“json_input”应为JSON类型。

enter image description here


2

对于那些不想写太多代码的人,有一种解决方法。

前端 -> 在POST方法中将您的JSON对象编码为字符串base64,在GET方法中解码为JSON。

In POST Method
data.components = btoa(JSON.stringify(data.components));

In GET
data.components = JSON.parse(atob(data.components))

后端 -> 在您的JPA代码中,将列更改为String或BLOB,无需转换。

@Column(name = "components", columnDefinition = "json")
private String components;

1
如果您正在使用JPA 2.1或更高版本,可以使用此案例。 链接 持久化Json对象 public class HashMapConverter implements AttributeConverter, String> {
@Override
public String convertToDatabaseColumn(Map<String, Object> customerInfo) {

    String customerInfoJson = null;
    try {
        customerInfoJson = objectMapper.writeValueAsString(customerInfo);
    } catch (final JsonProcessingException e) {
        logger.error("JSON writing error", e);
    }

    return customerInfoJson;
}

@Override
public Map<String, Object> convertToEntityAttribute(String customerInfoJSON) {

    Map<String, Object> customerInfo = null;
    try {
        customerInfo = objectMapper.readValue(customerInfoJSON, 
            new TypeReference<HashMap<String, Object>>() {});
    } catch (final IOException e) {
        logger.error("JSON reading error", e);
    }

    return customerInfo;
}

}

一个标准的JSON对象将把这些属性表示为HashMap:

@Convert(converter = HashMapConverter.class) private Map<String, Object> entityAttributes;


1
在这个更新的Spring Boot和MySQL版本中,下面的代码就足够了。
@Column( columnDefinition = "json" )
private String string;

我遇到了引号问题,所以我在我的项目中注释了下面这行代码。
#spring.jpa.properties.hibernate.globally_quoted_identifiers=true

0
在你的pom.xml中使用这个依赖项,利用Hibernate JSON类型。
    <dependency>
        <groupId>com.vladmihalcea</groupId>
        <artifactId>hibernate-types-52</artifactId>
        <version>2.10.4</version>
    </dependency>

在数据库中定义具有JSON数据类型的列,如下所示:

@Type(type = "json")
@Column(columnDefinition = "json")
private JsonNode column;

在@列注释中使用columnDefinition属性来告诉Hibernate,得分列应该映射到数据库中的JSON数据类型列。

0
我曾经遇到过类似的问题,通过使用@Externalizer注解和Jackson来序列化/反序列化数据解决了它(@Externalizer是OpenJPA特定的注解,因此您需要检查您的JPA实现是否有类似的可能性)。
@Persistent
@Column(name = "params")
@Externalizer("toJSON")
private Params params;

参数类实现:

public class Params {
    private static final ObjectMapper mapper = new ObjectMapper();

    private Map<String, Object> map;

    public Params () {
        this.map = new HashMap<String, Object>();
    }

    public Params (Params another) {
        this.map = new HashMap<String, Object>();
        this.map.putAll(anotherHolder.map);
    }

    public Params(String string) {
        try {
            TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {
            };
            if (string == null) {
                this.map = new HashMap<String, Object>();
            } else {
                this.map = mapper.readValue(string, typeRef);
            }
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }

    public String toJSON() throws PersistenceException {
        try {
            return mapper.writeValueAsString(this.map);
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }

    public boolean containsKey(String key) {
        return this.map.containsKey(key);
    }

    // Hash map methods
    public Object get(String key) {
        return this.map.get(key);
    }

    public Object put(String key, Object value) {
        return this.map.put(key, value);
    }

    public void remove(String key) {
        this.map.remove(key);
    }

    public Object size() {
        return map.size();
    }
}

HTH


感谢您的回答。我们使用的是“spring-data-jpa”。根据Maven所说,它依赖于org.eclipse.persistence和org.hibernate来进行JPA。这是否就是您所指的? - Rad
不,我们使用基于OpenJPA的JPA,而这个注释是OpenJPA特有的。我认为作为JPA提供者的Hibernate并没有提供这样的功能,只有当您将其用作普通Hibernate(HQL)而不是JPA时才会提供此功能。而Spring Data JPA正如其名称所示,使用JPA... - Magic Wand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接