Рубрики
Без рубрики

Как сопоставить столбцы Oracle JSON с помощью JPA и гибернации

Узнайте, как сопоставлять типы хранения столбцов Oracle JSON, будь то VARCHAR или BLOB, при использовании JPA и Hibernate, а также проекта hibernate-types.

Автор оригинала: Vlad Mihalcea.

Вступление

В этой статье мы рассмотрим, как сопоставить типы хранения столбцов Oracle JSON при использовании JPA и Hibernate.

Мой hibernate-типы проект уже давно поддерживает JSON для PostgreSQL и MySQL . Однако, разрабатывая свое новое и потрясающее Высокопроизводительное обучение SQL , я понял, что текущая реализация не обрабатывает типы столбцов Oracle JSON должным образом.

К счастью, исправление было простым, и, начиная с версии 2.7.0, теперь вы можете сохранять и извлекать атрибуты JSON в Oracle с помощью JPA и Hibernate.

Как сопоставить столбцы Oracle JSON с помощью JPA и гибернации @vlad_mihalcea https://t.co/8uJwu34okC pic.twitter.com/JuxTgIXxkK

Хранилище Oracle JSON

При использовании Oracle у вас есть два варианта сохранения объектов JSON. Вы можете использовать либо VARCHAR столбец, либо LOB тип хранения столбцов.

Если размер документа JSON не превышает 4000 байт, то лучше использовать тип столбца VARCHAR2(4000) . Если размер документа JSON составляет от 4000 до 32767 байт, вместо этого можно использовать тип столбца VARCHAR2(32767) .

Хранилище столбцов VARCHAR2(32767) является расширенным типом данных и использует LOB за кулисами. Первые 3500 байт хранятся внутри строки таблицы, поэтому для документов JSON, не превышающих 3500 байт, использование VARCHAR2(32767) вместо VARCHAR2(4000) оказывает небольшое влияние на производительность. Однако для больших документов JSON хранение и извлечение документа из базового хранилища LOB будет происходить медленнее, чем чтение и запись из встроенного хранилища строк таблицы.

Для больших документов JSON BLOB предпочтительнее, чем CLUB, поскольку для последнего требуется 2 байта для хранения каждого символа, что удваивает требования к хранилищу.

Сохранение JSON в виде VARCHAR

Давайте рассмотрим, что мы разрабатываем книжный интернет-магазин, и поэтому нам нужно использовать следующую таблицу книга база данных:

Для создания таблицы book мы можем использовать следующую инструкцию DDL:

CREATE TABLE book (
  id NUMBER(19, 0) NOT NULL PRIMARY KEY,
  isbn VARCHAR2(15 char),
  properties VARCHAR2(4000)
  CONSTRAINT ENSURE_JSON CHECK (properties IS JSON)
)

Обратите внимание, что свойства тип столбца VARCHAR2(4000) и мы определили ENSURE_JSON пользовательское ограничение, которое проверяет, хранит ли столбец свойства правильный объект JSON.

Чтобы сопоставить таблицу book с сущностью JPA, мы также должны решить, как обрабатывать столбец JSON. Поскольку столбец свойства имеет тип VARCHAR , мы можем отобразить его в виде Строки :

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
    name = "json", 
    typeClass = JsonType.class
)
public class Book {

    @Id
    private Long id;

    @NaturalId
    private String isbn;

    @Type(type = "json")
    private String properties;

    public Long getId() {
        return id;
    }

    public Book setId(Long id) {
        this.id = id;
        return this;
    }

    public String getIsbn() {
        return isbn;
    }

    public Book setIsbn(String isbn) {
        this.isbn = isbn;
        return this;
    }

    public String getProperties() {
        return properties;
    }

    public Book setProperties(String properties) {
        this.properties = properties;
        return this;
    }

    public JsonNode getJsonNodeProperties() {
        return JacksonUtil.toJsonNode(properties);
    }
}

Обратите внимание, что мы используем API в стиле Fluent для сеттеров, что позволит нам упростить процесс создания объекта.

Для получения более подробной информации об использовании построителей сущностей API в стиле Fluent ознакомьтесь с этой статьей .

Тип Json – это универсальный тип гибернации, который работает с большинством систем реляционных баз данных и является очень удобным выбором благодаря своей переносимости.

Теперь, когда сохраняется Книга сущность:

entityManager.persist(
    new Book()
        .setId(1L)
        .setIsbn("978-9730228236")
        .setProperties(
            "{" +
            "   \"title\": \"High-Performance Java Persistence\"," +
            "   \"author\": \"Vlad Mihalcea\"," +
            "   \"publisher\": \"Amazon\"," +
            "   \"price\": 44.99" +
            "}"
        )
);

Hibernate генерирует соответствующую инструкцию SQL INSERT:

INSERT INTO book (
    isbn, 
    properties, 
    id
) 
VALUES (
    '978-9730228236', 
    '{   
        "title": "High-Performance Java Persistence",   
        "author": "Vlad Mihalcea",   
        "publisher": "Amazon",   
        "price": 44.99
    }', 
    1
)

При извлечении сущности Book через ее естественный идентификатор мы видим , что Hibernate извлекает сущность просто отлично:

Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(Book.class)
    .load("978-9730228236");

assertEquals(
    "High-Performance Java Persistence",
    book.getJsonNodeProperties().get("title").asText()
);

Мы также можем изменить свойство сущности JSON:

book.setProperties(
    "{" +
    "   \"title\": \"High-Performance Java Persistence\"," +
    "   \"author\": \"Vlad Mihalcea\"," +
    "   \"publisher\": \"Amazon\"," +
    "   \"price\": 44.99," +
    "   \"url\": \"https://amzn.com/973022823X\"" +
    "}"
);

И, Hibernate выдаст соответствующую инструкцию обновления SQL:

UPDATE 
    book 
SET 
    properties =  
    '{   
        "title": "High-Performance Java Persistence",   
        "author": "Vlad Mihalcea",   
        "publisher": "Amazon",   
        "price": 44.99,   
        "url": "https://amzn.com/973022823X"
    }' 
WHERE 
    id = 1

Вы не ограничены использованием атрибута String entity. Вы также можете использовать POJO, учитывая, что свойства POJO соответствуют атрибутам JSON:

На этот раз атрибут свойства сущности будет отображен следующим образом:

@Type(type = "json")
private BookProperties properties;

Использование POJO вместо атрибута JSON на основе строк позволяет нам упростить операции чтения и записи на стороне приложения.

Обратите внимание, как хорошо мы можем создать экземпляр Книги сущности благодаря API в стиле Fluent, используемому как сущностью, так и классом POJO:

entityManager.persist(
    new Book()
        .setId(1L)
        .setIsbn("978-9730228236")
        .setProperties(
            new BookProperties()
                .setTitle("High-Performance Java Persistence")
                .setAuthor("Vlad Mihalcea")
                .setPublisher("Amazon")
                .setPrice(44.99D)
        )
);

Изменение атрибута свойства сущности также намного проще при использовании POJO:

Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(Book.class)
    .load("978-9730228236");

book.getProperties().setUrl(
    "https://amzn.com/973022823X"
);

Операторы SQL одинаковы независимо от того, используем ли мы строку или POJO на стороне JPA.

Хранение JSON в виде большого двоичного объекта

Если в нашей таблице book базы данных необходимо разместить очень большие объекты JSON, то вместо этого нам нужно использовать тип столбца BLOB:

Для создания таблицы book мы можем использовать следующую инструкцию DDL:

CREATE TABLE book (
  id NUMBER(19, 0) NOT NULL PRIMARY KEY,
  isbn VARCHAR2(15 char),
  properties BLOB
  CONSTRAINT ENSURE_JSON CHECK (properties IS JSON)
)
LOB (properties) STORE AS (CACHE)

Обратите внимание, что мы используем директиву STORE AS (КЭШ) , которая указывает Oracle размещать страницы LOB в буферном кэше, чтобы чтение и запись выполнялись быстрее.

Как и в предыдущем случае , когда мы использовали VARCHAR , мы можем сопоставить столбец BLOB JSON либо с Строкой , либо с POJO . В обоих случаях нам нужно использовать Тип большого двоичного объекта Json , предлагаемый проектом Hibernate Types.

Итак, при использовании атрибута сущности String JPA сопоставление выглядит следующим образом:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "jsonb", typeClass = JsonBlobType.class)
public class Book {

    @Id
    private Long id;

    @NaturalId
    private String isbn;

    @Type(type = "jsonb")
    private String properties;

    //Getters, setters and utility methods omitted for brevity 
}

И при использовании Свойства книги POJO сущность Книга отображается следующим образом:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(name = "jsonb", typeClass = JsonBlobType.class)
public class Book {

    @Id
    private Long id;

    @NaturalId
    private String isbn;

    @Type(type = "jsonb")
    private BookProperties properties;

    //Getters, setters and utility methods omitted for brevity 
}

При вставке той же сущности Book Hibernate выполнит следующую инструкцию SQL INSERT:

INSERT INTO book (
    isbn, 
    properties, 
    id
) 
VALUES (
    '978-9730228236', 
    org.hibernate.engine.jdbc.internal.BinaryStreamImpl@7d78f3d5, 
    1
)

При настройке столбца BLOB в Oracle Hibernate использует объект BinaryStreamImpl , который реализует интерфейс Java InputStream .

При изменении сущности Book Hibernate будет использовать объект BinaryStreamImpl для обновления столбца BLOB :

UPDATE 
    book 
SET 
    properties = org.hibernate.engine.jdbc.internal.BinaryStreamImpl@24d61e4 
WHERE 
    id = 1

Обратите внимание, что инструкция UPDATE задает только столбец BLOB , а не все столбцы, как в случае с механизмом обновления сущности по умолчанию.

Вывод

Проект hibernate-types со временем был расширен для размещения широкого спектра типов баз данных, а также для поддержки нескольких систем реляционных баз данных.

С выпуском 2.7.0 типы Hibernate теперь могут сопоставлять столбцы JSON с сущностями JPA во всех 4 ведущих системах баз данных: Oracle, MySQL, SQL Server и PostgreSQL.