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

Как сопоставить несколько объектов JPA с одной таблицей базы данных с помощью Hibernate

Узнайте, как сопоставить несколько объектов JPA с одной таблицей базы данных с помощью Hibernate. Использование нескольких сущностей может ускорить как операции чтения, так и записи.

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

Вступление

В этой статье мы рассмотрим, как мы можем сопоставить несколько объектов JPA с одной таблицей с помощью Hibernate. Использование нескольких сущностей может ускорить как операции чтения, так и записи.

Этот вопрос был повторяющейся темой в StackOverflow , поэтому я решил объяснить преимущества сопоставления нескольких объектов JPA с одной и той же таблицей базы данных.

Как сопоставить несколько объектов JPA с одной таблицей базы данных с помощью Hibernate @vlad_mihalcea https://t.co/YqBCZSlVuk pic.twitter.com/JbSov7BZrW

Модель предметной области

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

Обратите внимание, что столбец свойства имеет тип json . Следовательно, значение столбца properties равно значению содержащего объект JSON. По этой причине мы не хотим сопоставлять таблицу книга с одной сущностью Книга , потому что нам придется извлекать столбец свойства каждый раз, когда мы загружаем сущность Книга из базы данных.

Итак, мы сопоставим две сущности с таблицей книга . Одной сущностью является Краткое описание книги , которая отображает только подмножество столбцов книги таблицы. С другой стороны, сущность Книга отображает все столбцы из таблицы книга .

Как Краткое описание книги , так и Книга сущности расширяют класс Базовая книга абстрактный, как показано на следующей диаграмме.

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

@MappedSuperclass
public abstract class BaseBook {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    @Column(length = 15)
    private String isbn;

    @Column(length = 50)
    private String title;

    @Column(length = 50)
    private String author;

    public Long getId() {
        return id;
    }

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

    public String getIsbn() {
        return isbn;
    }

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

    public String getTitle() {
        return title;
    }

    public T setTitle(String title) {
        this.title = title;
        return (T) this;
    }

    public String getAuthor() {
        return author;
    }

    public T setAuthor(String author) {
        this.author = author;
        return (T) this;
    }
}

Аннотация Facebook использует аннотацию @MappedSuperclass , поскольку в противном случае атрибуты базового класса не наследуются сущностями, расширяющими класс базовой книги .

Для получения более подробной информации об аннотации @MappedSuperclass JPA ознакомьтесь с этой статьей .

Обратите внимание, что методы настройки используют шаблон интерфейса fluent, а возвращаемый тип задается параметром type, который может быть определен каждым расширяющимся классом, так что API fluent всегда возвращает ссылку на тип объекта, в которой определен вызывающий метод, а не ссылку на объект суперкласса.

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

Сущность Краткое описание книги просто расширяет Базовую книгу суперкласс и не добавляет никакого дополнительного атрибута сущности.

@Entity(name = "BookSummary")
@Table(name = "book")
public class BookSummary extends BaseBook {

}

С другой стороны, сущность Книга расширяет суперкласс Базовая книга и сопоставляет атрибут свойства .

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

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

    public String getProperties() {
        return properties;
    }

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

    public ObjectNode getJsonProperties() {
        return (ObjectNode) JacksonUtil
            .toJsonNode(properties);
    }
}

По умолчанию режим гибернации не поддерживает типы столбцов JSON. Однако вы можете использовать типы гибернации проект с открытым исходным кодом , который предоставляет большое разнообразие дополнительных типов гибернации, таких как JSON, массив, диапазон, HStore, типы перечислений PostgreSQL.

Время тестирования

При сохранении Книги сущности:

entityManager.persist(
    new Book()
        .setIsbn("978-9730228236")
        .setTitle("High-Performance Java Persistence")
        .setAuthor("Vlad Mihalcea")
        .setProperties(
            "{" +
                "   \"publisher\": \"Amazon\"," +
                "   \"price\": 44.99," +
                "   \"publication_date\": \"2016-20-12\"," +
                "   \"dimensions\": \"8.5 x 1.1 x 11 inches\"," +
                "   \"weight\": \"2.5 pounds\"," +
                "   \"average_review\": \"4.7 out of 5 stars\"," +
                "   \"url\": \"https://amzn.com/973022823X\"" +
            "}"
        )
);

Hibernate правильно устанавливает все книги столбцы строк таблицы:

INSERT INTO book (
    author, 
    isbn, title, 
    properties, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    {
        "publisher": "Amazon",
        "price": 44.99,
        "publication_date": "2016-20-12",
        "dimensions": "8.5 x 1.1 x 11 inches",
        "weight": "2.5 pounds",
        "average_review": "4.7 out of 5 stars",
        "url": "https:\/\/amzn.com\/973022823X"
        }, 
    1
)

Мы также можем сохранить Краткое описание книги для книги антипаттернов SQL для Билла Карвина :

entityManager.persist(
    new BookSummary()
        .setIsbn("978-1934356555")
        .setTitle("SQL Antipatterns")
        .setAuthor("Bill Karwin")
);

И Hibernate установит только столбцы, определенные сущностью Краткое описание книги :

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Bill Karwin', 
    '978-1934356555', 
    'SQL Antipatterns', 
    2
)

Мы можем получить Краткое описание книги для Высокопроизводительной сохраняемости Java книги следующим образом:

BookSummary bookSummary = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(BookSummary.class)
    .load("978-9730228236");

assertEquals(
    "High-Performance Java Persistence", 
    bookSummary.getTitle()
);

Поскольку Краткое описание книги является управляемой сущностью, мы можем изменить ее:

bookSummary.setTitle("High-Performance Java Persistence, 2nd edition");

А механизм проверки на наличие ошибок в спящем режиме обнаружит изменение и запустит инструкцию ОБНОВЛЕНИЯ при очистке контекста сохранения:

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    title = 'High-Performance Java Persistence, 2nd edition' 
WHERE 
    id = 1

Обратите внимание, что ОБНОВЛЕНИЕ выполняется только для атрибутов, определенных сущностью Краткое описание книги .

Теперь, в последующем контексте сохранения, мы также можем извлечь сущность Book , связанную со строкой таблицы высокопроизводительной книги сохранения Java.

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

assertEquals(
    "High-Performance Java Persistence, 2nd edition", 
    book.getTitle()
);

Поскольку сущность Книга также отображает атрибут свойства , мы можем как читать, так и записывать атрибут свойства .

ObjectNode jsonProperties = book.getJsonProperties();

assertEquals(
    "4.7 out of 5 stars", 
    jsonProperties.get("average_review").asText()
);

jsonProperties.put(
    "average_review", 
    "4.8 out of 5 stars"
);

book.setProperties(
    JacksonUtil.toString(jsonProperties)
);

При сбросе текущего контекста сохраняемости Hibernate выдаст инструкцию UPDATE, которая соответствующим образом установит столбец свойства :

UPDATE 
    book 
SET 
    properties = {
        "url": "https:\/\/amzn.com\/973022823X",
        "price": 44.99,
        "weight": "2.5 pounds",
        "publisher": "Amazon",
        "dimensions": "8.5 x 1.1 x 11 inches",
        "average_review": "4.8 out of 5 stars",
        "publication_date": "2016-20-12"
    } 
WHERE 
    id = 1

На этот раз Hibernate устанавливает только столбец свойства в инструкции UPDATE, поскольку сущность Book использует аннотацию @DynamicUpdate .

Для получения более подробной информации о том, как работает аннотация @DynamicUpdate при использовании JPA и гибернации, ознакомьтесь с этой статьей .

Вывод

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

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