Возможны ли типы JPA (EclipseLink)?

В частности, меня интересует использование типа PostgreSQLs json.

Ядро проблемы, похоже, состоит в том, что внутри Eclipselink до json нет внутреннего отображения. Итак, используя наивный подход с:

@Column(name = "json", columnDefinition = "json")
public String getJson() {
    return json;
}

... и пытаясь вставить объект, я получаю исключение:

Internal Exception: org.postgresql.util.PSQLException: ERROR: column "json" is of type json but expression is of type character varying

Достаточно честный, я полагаю.

Просматривая документы EclipseLink, кажется, что применимые настройки (Преобразования, Нативные Запросы, Преобразователи) зависят от данных, состоящих из поддерживаемых сопоставлений (числа, даты, строки и т.д.), поэтому это делает довольно неудобным обходитесь с использованием определенных типов поставщиков.

Основная причина, по которой это так расстраивает, заключается в том, что тип json в posgresql выражается так же, как text/varchar, и я верю (на данный момент, но не навсегда) является просто псевдонимом этого типа, поэтому драйвер более чем способный передать это, это просто правила проверки на моем пути.

В терминах решения я не против потери переносимости (с точки зрения агностики базы данных и использования специфических для поставщика типов), но просто хочу, чтобы решение позволяло мне использовать тип json как атрибут на обычном JPA Entity и сохранить все другое поведение, к которому он привык (генерация схемы, слияние, сохранение, транзакционный код

Ответ 1

Прогуливаясь по SO, я нашел много вопросов, подобных этому в JSON или типах XML для отображения в Postgres. Похоже, что никто не столкнулся с проблемой чтения из пользовательского типа Postgres, поэтому здесь решение для чтения и записи использует механизм преобразования типа JPA.

Драйвер Postgres JDBC отображает все атрибуты для неизвестных (для Java) типов в объект org.postgresql.util.PGobject, поэтому достаточно сделать конвертер для этого типа. Вот пример сущности:

@Entity
public class Course extends AbstractEntity {
    @Column(name = "course_mapped", columnDefinition = "json")
    @Convert(converter = CourseMappedConverter.class)
    private CourseMapped courseMapped;  // have no idea why would you use String json instead of the object to map

    // getters and setters
}

Вот пример конвертера:

@Converter
public class CourseMappedConverter implements AttributeConverter<CourseMapped, PGobject> {
    @Override
    public PGobject convertToDatabaseColumn(CourseMapped courseMapped) {
        try {
            PGobject po = new PGobject();
            // here we tell Postgres to use JSON as type to treat our json
            po.setType("json");
            // this is Jackson already added as dependency to project, it could be any JSON marshaller
            po.setValue((new ObjectMapper()).writeValueAsString(courseMapped));
            return po;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public CourseMapped convertToEntityAttribute(PGobject po) {
        try {
            return (new ObjectMapper()).readValue(po.getValue(),CourseMapped.class);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

Если вам действительно нужно придерживаться представления String JSON в вашей сущности, вы можете сделать такой конвертер для типа String

implements AttributeConverter<String, PGobject>

Здесь очень грязное (хотя и работающее) доказательство концепции, оно также использует сериализацию поддельных объектов, чтобы сообщить JPA, что объект был изменен, если он был

https://github.com/sasa7812/psql-cache-evict-POC

Ответ 2

PostgreSQL слишком строг в отношении неявных бросков между текстовыми типами. Самый простой способ - обходной путь, создавая бросок; см. этот ответ.

Чистым способом сделать это будет создание расширения поставщика JPA, которое вызывает setObject(my_json), и/или научит вашего провайдера JPA явно добавлять CAST('myvalue' AS json) при генерации запросов. Это боль, поскольку для этого требуются конкретные расширения провайдера JPA.

Этот поиск найдет кучу связанных вопросов для типа xml, с которыми у людей возникают схожие проблемы.