web-dev-qa-db-fra.com

Comment utiliser le type de données Postgres JSONB avec JPA?

Je ne trouve pas de moyen de mapper les types de données JSON et JSONB de PostgreSQL à l'aide de JPA (EclipseLink). Est-ce que quelqu'un utilise ces types de données avec JPA et peut me donner des exemples pratiques?

17
justcode

Toutes les réponses m'ont aidé à atteindre la solution finale qui est prête pour JPA et non EclipseLink ou Hibernate en particulier.

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);
    }
  }
}
22
justcode

Edit: Je vois maintenant que cela dépend à peu près de Hibernate. Mais vous pouvez peut-être trouver quelque chose de similaire pour EclipseLink ..

Je vais juste ajouter ce que j'ai comme réponse, elle provient d'une autre SO réponse mais peu importe .. Cela mappera jsonb à JsonObject de Google gson, mais vous pouvez le changer en quelque chose d'autre si nécessaire. Pour changer en quelque chose d'autre, changez les méthodes nullSafeGet, nullSafeSetet deepCopy.

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

Pour l'utiliser, faites:

public class SomeEntity {

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

En outre, vous devez définir votre dialecte pour indiquer que Java_OBJECT = jsonb:

registerColumnType(Types.Java_OBJECT, "jsonb");
5
Tobb

Je pense avoir trouvé une analogie avec le UserType d'Hibernate pour EclipseLink.

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

Vous devez créer une classe qui implémente org.Eclipse.persistence.mappings.converters.Converter et effectue la conversion pour vous, puis utilisez le @Convert annotation sur chaque champ où vous utilisez ce type.

2
coladict