Статьи

Как отобразить коллекции JSON с использованием JPA и Hibernate

Вступление

Проект hibernate-types открытым исходным кодом позволяет отображать объекты Java или JsonNode Джексона как свойства сущности JPA.

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

Maven зависимость

Прежде всего, вам нужно настроить следующую зависимость Maven в pom.xml конфигурации вашего проекта pom.xml :

1
2
3
4
5
<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

Если вы используете более старые версии Hibernate, обратитесь к репозиторию GitHub hibernate-types для получения дополнительной информации о зависимости соответствия для текущей версии Hibernate.

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

Предположим, у нас есть следующий тип объекта Location Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class Location implements Serializable {
 
    private String country;
 
    private String city;
 
    //Getters and setters omitted for brevity
 
    @Override
    public String toString() {
        return "Location{" +
                "country='" + country + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

И одна сущность Event :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity(name = "Event")
@Table(name = "event")
public class Event extends BaseEntity {
 
    @Type(type = "jsonb")
    @Column(columnDefinition = "jsonb")
    private Location location;
 
    @Type(
        type = "jsonb",
        parameters = {
            @org.hibernate.annotations.Parameter(
                name = TypeReferenceFactory.FACTORY_CLASS,
                value = "com.vladmihalcea.hibernate.type.json.PostgreSQLGenericJsonBinaryTypeTest$AlternativeLocationsTypeReference"
            )
        }
    )
    @Column(columnDefinition = "jsonb")
    private List<Location> alternativeLocations = new ArrayList<Location>();
 
    //Getters and setters omitted for brevity
}

BaseEntity определяет некоторые базовые свойства (например, @Id , @Version ) и несколько таможенных типов Hibernate, среди которых нас интересует JsonBinaryType .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@TypeDefs({
    @TypeDef(name = "string-array", typeClass = StringArrayType.class),
    @TypeDef(name = "int-array", typeClass = IntArrayType.class),
    @TypeDef(name = "json", typeClass = JsonStringType.class),
    @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class),
    @TypeDef(name = "jsonb-node", typeClass = JsonNodeBinaryType.class),
    @TypeDef(name = "json-node", typeClass = JsonNodeStringType.class),
})
@MappedSuperclass
public class BaseEntity {
 
    @Id
    private Long id;
 
    @Version
    private Integer version;
 
    //Getters and setters omitted for brevity
}

Чтобы узнать больше об использовании @MappedSuperclass , ознакомьтесь с этой статьей .

TypeReferenceFactory

Чтобы сохранить объект Location в jsonb PostgreSQL, нам просто нужно аннотировать свойство location с помощью @Type(type = "jsonb") .

Однако для коллекции alternativeLocations нам необходимо предоставить связанный TypeReference Jackson TypeReference чтобы мы могли восстановить ту же самую безопасную для типов коллекцию Java при чтении объекта JSON из реляционной базы данных.

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

1
2
3
4
5
6
7
8
public static class AlternativeLocationsTypeReference
    implements TypeReferenceFactory {
     
    @Override
    public TypeReference<?> newTypeReference() {
        return new TypeReference<List<Location>>() {};
    }
}

Это оно!

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

При сохранении следующего объекта Event :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
Location cluj = new Location();
cluj.setCountry("Romania");
cluj.setCity("Cluj-Napoca");
 
Location newYork = new Location();
newYork.setCountry("US");
newYork.setCity("New-York");
 
Location london = new Location();
london.setCountry("UK");
london.setCity("London");
 
Event event = new Event();
event.setId(1L);
event.setLocation(cluj);
event.setAlternativeLocations(
    Arrays.asList(newYork, london)
);
 
entityManager.persist(event);

Hibernate сгенерирует следующий оператор SQL INSERT:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
INSERT INTO event (
    version,
    alternativeLocations,
    location,
    id
)
VALUES (
    0,
    [
        {"country":"US","city":"New-York"},
        {"country":"UK","city":"London"}
    ],
    {"country":"Romania","city":"Cluj-Napoca"},
    1
)

Кроме того, при получении обратно объекта Event , свойства location и alternativeLocations правильно выбираются:

Событие события = entityManager.find (Event.class, eventId);

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
assertEquals(
    "Cluj-Napoca",
    event.getLocation().getCity()
);
 
assertEquals(2, event.getAlternativeLocations().size());
 
assertEquals(
    "New-York",
    event.getAlternativeLocations().get(0).getCity()
);
assertEquals(
    "London",
    event.getAlternativeLocations().get(1).getCity()
);

Круто, верно?

Смотрите оригинальную статью здесь: Как отобразить коллекции JSON с использованием JPA и Hibernate

Мнения, высказанные участниками Java Code Geeks, являются их собственными.