Автор оригинала: Vlad Mihalcea.
Вступление
Один из моих подписчиков в Твиттере недавно спросил меня о способе хранения EAV (Сущность-Атрибут-Значение) данных с использованием JPA и гибернации, и, поскольку это очень хороший вопрос, я решил превратить его в сообщение в блоге.
Слава @vlad_mihalcea , библиотека типов гибернации вчера сэкономила мне массу работы, автоматически сопоставив столбец PostgreSQL jsonb с POJO. Блестящая работа 💪
В этой статье вы увидите, как можно использовать типы JSON для хранения данных EAV без схемы.
Модель предметной области
Давайте предположим, что у нас есть книжный интернет-магазин, и книги моделируются с помощью объекта Книга
:
@Entity(name = "Book") @Table(name = "book") @TypeDef( typeClass = JsonType.class, defaultForType = JsonNode.class ) public class Book { @Id @GeneratedValue private Long id; @NaturalId private String isbn; @Column(columnDefinition = "jsonb") private JsonNode properties; //Getters and setters omitted for brevity }
Книга
имеет уникальный атрибут isbn
, поэтому она отображается как @Natural
.
Для получения более подробной информации об аннотации @Natural
ознакомьтесь с этой статьей .
Атрибут свойства
имеет тип JsonNode
, который содержит структуру данных EAV.
Поскольку Hibernate изначально не поддерживает типы JSON , нам нужен тип Json
для обработки специфичного для Джексона JsonNode
типа объекта.
Типы спящего режима
Первое, что вам нужно сделать, это настроить следующую зависимость Maven в вашем проекте pom.xml
файл конфигурации:
com.vladmihalcea hibernate-types-55 ${hibernate-types.version}
Вот и все!
Время тестирования
При сохранении следующей Книги
сущности:
Book book = new Book(); book.setIsbn( "978-9730228236" ); book.setProperties( JacksonUtil.toJsonNode( "{" + " \"title\": \"High-Performance Java Persistence\"," + " \"author\": \"Vlad Mihalcea\"," + " \"publisher\": \"Amazon\"," + " \"price\": 44.99" + "}" ) ); entityManager.persist( book );
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 позаботится обо всей логике сохранения:
Session session = entityManager.unwrap( Session.class ); Book book = session .bySimpleNaturalId( Book.class ) .load( "978-9730228236" ); LOGGER.info( "Book details: {}", book.getProperties() ); book.setProperties( JacksonUtil.toJsonNode( "{" + " \"title\": \"High-Performance Java Persistence\"," + " \"author\": \"Vlad Mihalcea\"," + " \"publisher\": \"Amazon\"," + " \"price\": 44.99," + " \"url\": \"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" + "}" ) );
При запуске приведенного выше тестового случая Hibernate генерирует следующие инструкции SQL:
SELECT b.id AS id1_0_ FROM book b WHERE b.isbn = '978-9730228236' SELECT b.id AS id1_0_0_ , b.isbn AS isbn2_0_0_ , b.properties AS properti3_0_0_ FROM book b WHERE b.id = 1 -- Book details: {"price":44.99,"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon"} UPDATE book SET properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/"}' WHERE id = 1
Круто, правда?
Вывод
Типы гибернации-это здорово! Даже если Hibernate поставляется с большим разнообразием типов, даже с датой и временем Java 1.8 , вы можете легко сопоставлять пользовательские типы, такие как массив или JSON.
Для JSON вы можете не только сопоставлять структуры JSON как объекты Java, как мы видели в предыдущей статье , но и извлекать выгоду из структур без схем.