JPA将JSON列映射到Java Object

Rad*_*Rad 17 java mysql orm json jpa

我们有一个包含大量列的大表.在我们转移到MySQL Cluster之后,由于以下原因无法创建表:

ERROR 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;
}
Run Code Online (Sandbox Code Playgroud)

它是用于存储配置参数的表.我想我们可以将一些列组合成一个并将其存储为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;
}
Run Code Online (Sandbox Code Playgroud)

我们定义的地方:

public class Params implements Serializable
{
    private ParamA parama;
    private ParamB paramb;
}
Run Code Online (Sandbox Code Playgroud)

通过使用它,我们可以将所有列合并为一个并创建我们的表.或者我们可以将整个表分成几个表.我个人更喜欢第一种解决方案.

无论如何我的问题是如何映射Params列,它是文本并包含Java对象的JSON字符串?

Ale*_*ini 43

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

@Convert(converter = JpaConverterJson.class)
Run Code Online (Sandbox Code Playgroud)

然后以类似的方式创建类(这会转换一个通用的Object,你可能想要专门化它):

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

}
Run Code Online (Sandbox Code Playgroud)

就是这样:您可以使用此类将任何对象序列化为表中的json.

  • 只是感觉应该有一个注释可以为我们做到这一点。这是一个非常通用的用例,似乎是很多人都想要的。可惜我还没找到。谢谢您的回答。我最终自己做了这件事,在寻找更好的方法时,我偶然发现了你的答案,它与我的答案基本相同。不幸的是,这只是让我放心,我的方式是“最干净的”。 (3认同)
  • @Converter(autoApply = true) 不要将此转换器放在类上,因为在序列化和反序列化时,它会自动将此转换器应用于所有存在日期类型和日期时间 SQL 类型问题的对象 (3认同)
  • 如果存储在 DB 中的 json 是一个数组,意思是:[{...},{...},{...}],转换器会抛出异常还是映射器会处理它? (2认同)
  • JPA 将在 `LinkedHashMap` 中返回结果,jso-db 中的每个 Json 属性都会因为 object.class 被转换为 `LinkedHashMap` (2认同)

小智 9

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

@Column(name = "params", columnDefinition = "json")
@JsonRawValue
private String params;
Run Code Online (Sandbox Code Playgroud)

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


Vla*_*cea 5

正如我在本文中解释的那样,JPA AttributeConverter太局限了,无法映射JSON对象类型,特别是如果您要将其保存为JSON二进制文件。

您不必手动创建所有这些类型,只需使用以下依赖关系通过Maven Central获取它们:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version> 
</dependency> 
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请查看hibernate-types开源项目

现在,解释一下它是如何工作的。

我写了一篇关于如何在PostgreSQL和MySQL上映射JSON对象的文章

对于PostgreSQL,您需要以二进制形式发送JSON对象:

public class JsonBinaryType
    extends AbstractSingleColumnStandardBasicType<Object> 
    implements DynamicParameterizedType {

    public JsonBinaryType() {
        super( 
            JsonBinarySqlTypeDescriptor.INSTANCE, 
            new JsonTypeDescriptor()
        );
    }

    public String getName() {
        return "jsonb";
    }

    @Override
    public void setParameterValues(Properties parameters) {
        ((JsonTypeDescriptor) getJavaTypeDescriptor())
            .setParameterValues(parameters);
    }

}
Run Code Online (Sandbox Code Playgroud)

JsonBinarySqlTypeDescriptor如下所示:

public class JsonBinarySqlTypeDescriptor
    extends AbstractJsonSqlTypeDescriptor {

    public static final JsonBinarySqlTypeDescriptor INSTANCE = 
        new JsonBinarySqlTypeDescriptor();

    @Override
    public <X> ValueBinder<X> getBinder(
        final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicBinder<X>(javaTypeDescriptor, this) {
            @Override
            protected void doBind(
                PreparedStatement st, 
                X value, 
                int index, 
                WrapperOptions options) throws SQLException {
                st.setObject(index, 
                    javaTypeDescriptor.unwrap(
                        value, JsonNode.class, options), getSqlType()
                );
            }

            @Override
            protected void doBind(
                CallableStatement st, 
                X value, 
                String name, 
                WrapperOptions options)
                    throws SQLException {
                st.setObject(name, 
                    javaTypeDescriptor.unwrap(
                        value, JsonNode.class, options), getSqlType()
                );
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

JsonTypeDescriptor像这样:

public class JsonTypeDescriptor
        extends AbstractTypeDescriptor<Object> 
        implements DynamicParameterizedType {

    private Class<?> jsonObjectClass;

    @Override
    public void setParameterValues(Properties parameters) {
        jsonObjectClass = ( (ParameterType) parameters.get( PARAMETER_TYPE ) )
            .getReturnedClass();

    }

    public JsonTypeDescriptor() {
        super( Object.class, new MutableMutabilityPlan<Object>() {
            @Override
            protected Object deepCopyNotNull(Object value) {
                return JacksonUtil.clone(value);
            }
        });
    }

    @Override
    public boolean areEqual(Object one, Object another) {
        if ( one == another ) {
            return true;
        }
        if ( one == null || another == null ) {
            return false;
        }
        return JacksonUtil.toJsonNode(JacksonUtil.toString(one)).equals(
                JacksonUtil.toJsonNode(JacksonUtil.toString(another)));
    }

    @Override
    public String toString(Object value) {
        return JacksonUtil.toString(value);
    }

    @Override
    public Object fromString(String string) {
        return JacksonUtil.fromString(string, jsonObjectClass);
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        if ( String.class.isAssignableFrom( type ) ) {
            return (X) toString(value);
        }
        if ( Object.class.isAssignableFrom( type ) ) {
            return (X) JacksonUtil.toJsonNode(toString(value));
        }
        throw unknownUnwrap( type );
    }

    @Override
    public <X> Object wrap(X value, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        return fromString(value.toString());
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,您需要在类级别或在package-info.java包级别描述符中声明新类型:

@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
Run Code Online (Sandbox Code Playgroud)

实体映射将如下所示:

@Type(type = "jsonb")
@Column(columnDefinition = "json")
private Location location;
Run Code Online (Sandbox Code Playgroud)

如果您使用的是Hibernate 5或更高版本,则Postgre92Dialect自动注册JSON类型。

否则,您需要自己注册:

public class PostgreSQLDialect extends PostgreSQL91Dialect {

    public PostgreSQLDialect() {
        super();
        this.registerColumnType( Types.JAVA_OBJECT, "json" );
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

很简单

@Column(name = "json_input", columnDefinition = "json")
private String field;
Run Code Online (Sandbox Code Playgroud)

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

在此处输入图片说明