Как использовать тип данных Postgres JSONB с JPA?

#java #json #postgresql #jpa #eclipselink

#java #json #postgresql #jpa #eclipselink

Вопрос:

Я не нахожу способ сопоставить типы данных JSON и JSONB из PostgreSQL с помощью JPA (EclipseLink). Кто-нибудь использует эти типы данных с JPA и может дать мне несколько рабочих примеров?

Комментарии:

1. Где-то на SO есть пример, позвольте мне посмотреть, смогу ли я найти…

Ответ №1:

Все ответы помогли мне прийти к окончательному решению, готовому для 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 amp;amp; ((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);
    }
  }
}
  

Комментарии:

1. Я пробовал что-то подобное для гибернации, но, похоже AttributeConverter , в итоге Object это не работает Hibernate . Я просто получаю сообщение об ошибке «неизвестный тип jdbc» или что-то в этом роде. Позор действительно, так как этот подход намного красивее, с меньшим количеством котлов.

2. Технически, вы правы, что это должно работать. У веб-приложения и сервера нет веских причин загружать класс из разных загрузчиков классов, но они это делают.

3. Это решение не работает с гибернацией (текущая версия 5.2.10.Final). Ошибка есть org.hibernate.MappingException: No Dialect mapping for JDBC type: SOME_RANDOM_NUMBER , и, по-видимому, разработчики не собираются ее исправлять. Предлагаемое ими решение использует общие типы гибернации (тяжелые вещи) vladmihalcea.com/2016/06/20 /…

4. @SewerynNiemiec Вы правильно используете JPA? Это решение предназначено для использования с JPA, а не с конкретным диалектом HQL из Hibernate. Это ваш случай?

5. Вы должны заменить mapper.read на mapper.readFor, поскольку mapper.read устарел.

Ответ №2:

Редактировать: теперь я вижу, что это в значительной степени Hibernate зависит. Но, возможно, вы можете найти что-то подобное для EclipseLink .

Я просто добавлю то, что у меня есть в качестве ответа, оно исходит из другого ответа SO, но неважно. Это будет отображаться jsonb в JsonObject Google gson , но при необходимости вы можете изменить его на что-то другое. Чтобы перейти на что-то другое, измените nullSafeGet nullSafeSet и 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);
    }
}
  

Чтобы использовать это, выполните:

 public class SomeEntity {

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

Кроме того, вам необходимо указать свой диалект, чтобы указать, что JAVA_OBJECT = jsonb :

 registerColumnType(Types.JAVA_OBJECT, "jsonb");
  

Ответ №3:

Я думаю, что нашел аналогию с типом пользователя Hibernate для EclipseLink.

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

Вы должны создать класс, который реализует org.eclipse.persistence.mappings.converters.Converter и выполняет преобразование для вас, а затем использовать @Convert аннотации для каждого поля, в котором вы используете этот тип.

Ответ №4:

Для тех, кто ищет решение Mysql с типом столбца JSON, вот оно. FWIW Я использую 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());
    }

}