【问题标题】:How to use Postgres JSONB datatype with JPA?如何在 JPA 中使用 Postgres JSONB 数据类型?
【发布时间】:2017-02-11 08:28:22
【问题描述】:

我没有找到使用 JPA (EclipseLink) 从 PostgreSQL 映射 JSON 和 JSONB 数据类型的方法。有人在 JPA 中使用这种数据类型,可以给我一些工作示例吗?

【问题讨论】:

  • SO上有一个例子,让我看看能不能找到...

标签: java json postgresql jpa eclipselink


【解决方案1】:

编辑:我现在看到这几乎与 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】:

    我想我找到了与 Hibernate 的 EclipseLink 的 UserType 的类比。

    http://www.eclipse.org/eclipselink/documentation/2.6/jpa/extensions/annotations_ref.htm#CHDEHJEB

    您必须创建一个实现org.eclipse.persistence.mappings.converters.Converter 并为您进行转换的类,然后在您使用该类型的每个字段上使用@Convert 注释。

    【讨论】:

      【解决方案3】:

      所有答案都帮助我找到了为 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);
          }
        }
      }
      

      【讨论】:

      • 我已经为 Hibernate 尝试过类似的方法,但似乎以 Object 结尾的 AttributeConverterHibernate 破坏了。我只是收到一个错误,说“未知的 jdbc 类型”或类似的东西。真的很遗憾,因为这种方法更漂亮,样板更少。
      • 从技术上讲,它应该可以工作是对的。 webapp 和服务器没有充分的理由从不同的 ClassLoader 加载类,但他们正在这样做。
      • 此解决方案不适用于 Hibernate(当前为 5.2.10.Final)。错误是org.hibernate.MappingException: No Dialect mapping for JDBC type: SOME_RANDOM_NUMBER,显然开发人员不会修复它。他们提出的解决方案是使用 Generic Hibernate Types (heavy stuff) vladmihalcea.com/2016/06/20/…
      • @SewerynNiemiec 你在使用 JPA 吗?此解决方案适用于 JPA,而不适用于 Hibernate 的特定方言 HQL。你的情况是这样吗?
      • 你必须用 mapper.readFor 替换 mapper.read,因为 mapper.read 已被弃用。
      猜你喜欢
      • 2018-05-31
      • 1970-01-01
      • 2017-10-14
      • 1970-01-01
      • 2020-07-01
      • 2019-03-20
      • 1970-01-01
      • 2019-08-03
      • 1970-01-01
      相关资源
      最近更新 更多