如何在JPA中使用Postgres的JSONB数据类型?

32

我无法找到一种使用JPA(EclipseLink)映射PostgreSQL的JSON和JSONB数据类型的方法。是否有人正在使用这些数据类型及JPA,并能提供一些有效的示例?


有一个示例在 Stack Overflow 上,让我看看能否找到... - Tobb
4个回答

30

所有的回答都帮助我得出最终解决方案,该方案适用于JPA而不是特定于EclipseLink或Hibernate。

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import javax.json.Json;
import javax.json.JsonObject;
import javax.persistence.Converter;
import org.postgresql.util.PGobject;

@Converter(autoApply = true)
public class JsonConverter implements javax.persistence.AttributeConverter<JsonObject, Object> {

  private static final long serialVersionUID = 1L;
  private static ObjectMapper mapper = new ObjectMapper();

  @Override
  public Object convertToDatabaseColumn(JsonObject objectValue) {
    try {
      PGobject out = new PGobject();
      out.setType("json");
      out.setValue(objectValue.toString());
      return out;
    } catch (Exception e) {
      throw new IllegalArgumentException("Unable to serialize to json field ", e);
    }
  }

  @Override
  public JsonObject convertToEntityAttribute(Object dataValue) {
    try {
      if (dataValue instanceof PGobject && ((PGobject) dataValue).getType().equals("json")) {
        return mapper.reader(new TypeReference<JsonObject>() {
        }).readValue(((PGobject) dataValue).getValue());
      }
      return Json.createObjectBuilder().build();
    } catch (IOException e) {
      throw new IllegalArgumentException("Unable to deserialize to json field ", e);
    }
  }
}

4
我尝试在Hibernate中使用类似的方法,但是似乎以Object结束的AttributeConverter与Hibernate不兼容。我只能得到一个错误,提示“未知的jdbc类型”之类的信息。真遗憾,因为这种方法更简洁,没有那么多样板代码。 - Tobb
1
从技术上讲,你是正确的,它应该可以工作。没有什么好的理由让Web应用程序和服务器从不同的类加载器中加载类,但它们确实这样做了。 - coladict
9
这个解决方案不适用于Hibernate(当前版本为5.2.10.Final)。错误信息是“org.hibernate.MappingException:No Dialect mapping for JDBC type: SOME_RANDOM_NUMBER”,显然开发者不会修复它。他们提出的解决方案是使用通用Hibernate类型(比较复杂)https://vladmihalcea.com/2016/06/20/how-to-map-json-objects-using-generic-hibernate-types/ - Seweryn Niemiec
1
@SewerynNiemiec 你是否正确使用JPA?这个解决方案是用于与JPA一起使用,而不是Hibernate特定方言的HQL。你的情况是这样吗? - justcode
5
您需要将mapper.read替换为mapper.readFor,因为mapper.read已经过时。 - Prachi
显示剩余7条评论

6

编辑:我现在看到这基本上是依赖于Hibernate的。但或许你可以找到类似于EclipseLink的东西。

我将附加我的答案,它来自另一个SO答案,但不管怎样。这将把jsonb映射为Google的gsonJsonObject,但如果需要,您可以将其更改为其他内容。要更改为其他内容,请更改nullSafeGetnullSafeSetdeepCopy方法。

public class JsonbType implements UserType {

    @Override
    public int[] sqlTypes() {
        return new int[] { Types.JAVA_OBJECT };
    }

    @Override
    public Class<JsonObject> returnedClass() {
        return JsonObject.class;
    }

    @Override
    public boolean equals(final Object x, final Object y) {
        if (x == y) {
            return true;
        }
        if (x == null || y == null) {
            return false;
        }
        return x.equals(y);
    }

    @Override
    public int hashCode(final Object x) {
        if (x == null) {
            return 0;
        }

        return x.hashCode();
    }

    @Nullable
    @Override
    public Object nullSafeGet(final ResultSet rs,
                              final String[] names,
                              final SessionImplementor session,
                              final Object owner) throws SQLException {
        final String json = rs.getString(names[0]);
        if (json == null) {
            return null;
        }

        final JsonParser jsonParser = new JsonParser();
        return jsonParser.parse(json).getAsJsonObject();
    }

    @Override
    public void nullSafeSet(final PreparedStatement st,
                            final Object value,
                            final int index,
                            final SessionImplementor session) throws SQLException {
        if (value == null) {
            st.setNull(index, Types.OTHER);
            return;
        }

        st.setObject(index, value.toString(), Types.OTHER);
    }

    @Nullable
    @Override
    public Object deepCopy(@Nullable final Object value) {
        if (value == null) {
            return null;
        }
        final JsonParser jsonParser = new JsonParser();
        return jsonParser.parse(value.toString()).getAsJsonObject();
    }

    @Override
    public boolean isMutable() {
        return true;
    }

    @Override
    public Serializable disassemble(final Object value) {
        final Object deepCopy = deepCopy(value);

        if (!(deepCopy instanceof Serializable)) {
            throw new SerializationException(
                    String.format("deepCopy of %s is not serializable", value), null);
        }

        return (Serializable) deepCopy;
    }

    @Nullable
    @Override
    public Object assemble(final Serializable cached, final Object owner) {
        return deepCopy(cached);
    }

    @Nullable
    @Override
    public Object replace(final Object original, final Object target, final Object owner) {
        return deepCopy(original);
    }
}

使用方法:

public class SomeEntity {

    @Column(name = "jsonobject")
    @Type(type = "com.myapp.JsonbType") 
    private JsonObject jsonObject;

此外,您需要设置方言以指示 JAVA_OBJECT = jsonb:

registerColumnType(Types.JAVA_OBJECT, "jsonb");

2

1

对于任何想要使用JSON列类型的Mysql解决方案的人,这里有一个解决方案。值得一提的是,我正在使用EclipseLink,但这是一个纯JPA解决方案。

@Column(name = "JSON_DATA", columnDefinition="JSON")
@Convert(converter=JsonAttributeConverter.class)
private Object jsonData;

并且

@Converter
public class JsonAttributeConverter implements AttributeConverter <Object, String>
{

    private JsonbConfig cfg = new JsonbConfig().withFormatting(true);

    private Jsonb jsonb = JsonbBuilder.create(cfg);
    
    @Override
    public String convertToDatabaseColumn(Object object)
    {      
        if (object == null) return null;

        return jsonb.toJson(object);
    }

    @Override
    public Object convertToEntityAttribute(String value)
    {
        if (value == null) return null;

        return jsonb.fromJson(value, value.getClass());
    }

}

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