繁体   English   中英

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

[英]How to map a map JSON column to Java Object with JPA

我们有一张有很多列的大桌子。 迁移到 MySQL Cluster 后,无法创建表,原因是:

错误 1118 (42000):行大小太大。 使用的表类型的最大行大小(不包括 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 字符串的 Params 列?

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

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

JPA AttributeConverter对映射 JSON 对象类型的限制太多,尤其是当您想将它们保存为 JSON 二进制文件时。

您不必创建自定义 Hibernate Type 来获得 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.dialect Hibernate 属性设置为使用您刚刚创建的MySQLJsonDialect类的完全限定类名。

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

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

这可能不会为服务器端使用做 DTO 映射,但客户端会得到正确格式化为 json 的属性。

很简单

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

并在 mysql 数据库中您的列 'json_input' json 类型

在此处输入图片说明

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

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

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

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

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

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

我有一个类似的问题,并通过使用 @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

为什么我们真正使用 JSON / BLOB ?

  1. 当有很多字段并在更新时,我们可能希望更新特定字段而让其他字段不发生变化。 在这种情况下,目前我们需要读取现有值,全部到客户端,然后与新更改合并,然后更新所有值。 相反,现在 json 合并补丁操作将执行所需的更新,而无需知道其他人的实际值。
  2. 当存在太多字段并且这些字段可以更改并且我们不想对代码进行太多更改时,我们可以使用 json / blob。 在这里,服务器端,我们存储为 json / blob 并仅将索引字段保留为表列中的非 json 字段,以便搜索条件不需要涉及太多的 json 验证函数调用。 现在,我们从没想过,剩余的非索引列会被划分回连接表。 我们正在妥协,将不经常更改的值的多个副本作为 json 对象的一部分。 当必须更改这些值时,与其进行更快的更新,我们更喜欢使用数据库功能或控制器/服务级别功能,它们将通过进行整个表扫描来更新所有 json/blob。
  3. 这将确保,当用户登录时,可以在没有表连接的情况下以单个选择操作的形式获取所有必需的详细信息,并且所有必需的信息都针对单盘访问进行了本地化,以成倍地提高速度并将其余的 json 操作留给客户端,正如我们所期望的浏览器大部分时间都处于空闲状态,可以在没有太多开销/等待时间的情况下完成这项工作。
  4. 像 angular 这样的应用程序具有数据服务,可以通过一次读取来存储这些类型的数据,并且这些数据根据需要在所有视图组件中使用
  5. 所以,理想情况下,我们必须期待,
  6. 服务器端,我们将有更少的表,避免加入表和客户端,许多 json 对象和详细的工作。

相反,如果您在服务器端使用了过多的连接并再次使用 blob,则意味着设计不佳

在这个较新版本的 Spring Boot 和 MySQL 中,下面的代码就足够了

@Column( columnDefinition = "json" )
private String string;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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