Java >> Java tutorial >  >> Tag >> SQL

Sådan bruger du PostgreSQL's JSONB-datatype med Hibernate

De fleste databaser tilbyder masser af proprietære funktioner ud over den kendte SQL-standard. Et eksempel er PostgreSQL's JSONB datatype, som giver dig mulighed for at gemme JSON-dokumenter effektivt i en databasekolonne.

Du kan selvfølgelig også gemme JSON-dokumentet i en tekstkolonne. Det er en del af SQL-standarden og understøttes af Hibernate og alle andre JPA-implementeringer. Alligevel ville du gå glip af PostgreSQL-specifikke funktioner som JSON-validering og en liste over interessante JSON-funktioner og -operatører. Men du er sikkert allerede klar over det, hvis du læser dette indlæg.

Hvis du vil bruge en JSONB-kolonne med Hibernate 6, har jeg gode nyheder til dig. Hibernate 6 giver en standardmapping for entitetsattributter til JSON-kolonner; du behøver kun at aktivere den. Desværre understøtter Hibernate 4 og 5 ikke nogen JSON-tilknytninger, og du skal implementere en UserType . Jeg vil vise dig begge muligheder i dette indlæg.

Databasetabel og enhed

Lad os tage et hurtigt kig på databasetabellen og entiteten, før vi kommer ind på detaljerne i UserType .
Som du kan se i følgende kodestykke, er definitionen af ​​databasetabellen meget enkel og består kun af 2 kolonner:den primære nøglekolonne id og kolonnen jsonproperty af typen JSONB .

CREATE TABLE myentity
(
  id bigint NOT NULL,
  jsonproperty jsonb,
  CONSTRAINT myentity_pkey PRIMARY KEY (id)
)

Og du kan se den enhed, der kortlægger tabellen, i følgende kodestykke.

@Entity
public class MyEntity {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private MyJson jsonProperty;
     
    ...
}

Som du kan se, er der ikke noget JSON-specifikt på denne enhed, kun en attribut af typen MyJson . MyJson er en simpel POJO med 2 egenskaber, som du kan se i næste kodestykke.

public class MyJson implements Serializable {
 
    private String stringProp;
     
    private Long longProp;
 
    public String getStringProp() {
        return stringProp;
    }
 
    public void setStringProp(String stringProp) {
        this.stringProp = stringProp;
    }
 
    public Long getLongProp() {
        return longProp;
    }
 
    public void setLongProp(Long longProp) {
        this.longProp = longProp;
    }
}

Så hvad skal du gøre, hvis du vil gemme MyJson ejendom i en JSONB database kolonne? Svaret på det afhænger af den Hibernate-version, du bruger.

I Hibernate 4 og 5 skal du implementere en tilpasset typetilknytning. Bare rolig. Det er ikke så kompliceret, som det måske lyder. Du behøver kun at implementere UserType interface og registrer din typekortlægning. Jeg vil vise dig, hvordan du gør det i denne artikel.

Hibernate 6 gør alt dette endnu nemmere. Det giver en standard JSON-mapping, som du skal aktivere. Lad os tage et kig på dette først.

JSONB kortlægning i Hibernate 6

Tak til JSON kortlægning introduceret i Hibernate 6, behøver du kun at annotere din enhedsattribut med en @JdbcTypeCode annotation og indstil typen til SqlTypes.JSON . Hibernate registrerer derefter et JSON-bibliotek på din klassesti og bruger det til at serialisere og deserialisere attributtens værdi.

@Entity
public class MyEntity {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @JdbcTypeCode(SqlTypes.JSON)
    private MyJson jsonProperty;
     
    ...
}

 @JdbcTypeCode annotation er en ny annotation, der blev introduceret som en del af Hibernates nye typemapping. Fra og med Hibernate 6 kan du definere Java- og JDBC-tilknytningen separat ved at annotere din enhedsattribut med en @JdbcTypeCode eller @JavaType anmærkning. Ved at bruge disse annoteringer kan du referere til en af ​​Hibernates standardtilknytninger eller dine egne implementeringer af JavaTypeDescriptor  eller JdbcTypeDescriptor  grænseflader. Jeg vil forklare implementeringen af ​​disse grænseflader i en anden tutorial. Vi behøver kun at aktivere Hibernates standardkortlægning.

Når du har annoteret din enhedsattribut for at aktivere Hibernates JSON-tilknytning, kan du bruge entiteten og dens attribut i din virksomhedskode. Jeg forberedte et eksempel på det i slutningen af ​​denne artikel.

JSONB-kortlægning i Hibernate 4 og 5

Som jeg nævnte tidligere, skal du implementere en tilpasset kortlægning, hvis du vil bruge PostgreSQL's JSONB skriv med Hibernate 4 eller 5. Den bedste måde at gøre det på er at implementere Hibernates UserType interface og registrer kortlægningen på en brugerdefineret dialekt.

Implementer en Hibernate UserType

Du skal først oprette en Hibernate UserType , som kortlægger MyJson objekt ind i et JSON-dokument og definerer tilknytningen til en SQL-type. Jeg kalder UserType MyJsonType og vis kun de vigtigste metoder i følgende kodestykker. Du kan se hele klassen i GitHub-lageret.

Der er et par vigtige ting, du skal gøre, hvis du vil implementere din egen UserType . Først og fremmest skal du implementere metoderne sqlTypes og returnedClass , som fortæller Hibernate SQL-typen og Java-klassen den skal bruge til denne kortlægning. I dette tilfælde bruger jeg den generiske Type.JAVA_OBJECT som SQL-typen og selvfølgelig MyJson klasse som Java-klassen.

public class MyJsonType implements UserType {
 
    @Override
    public int[] sqlTypes() {
        return new int[]{Types.JAVA_OBJECT};
    }
 
    @Override
    public Class<MyJson> returnedClass() {
        return MyJson.class;
    }
     
    ...
}

Så skal du implementere metoderne nullSafeGet og nullSafeSet , som Hibernate kalder, når du læser eller skriver attributten.

nullSafeGet metode bliver kaldt for at kortlægge værdien fra databasen til Java-klassen. Så vi er nødt til at parse JSON-dokumentet til en MyJson klasse. Jeg bruger Jackson ObjectMapper her, men du kan også bruge enhver anden JSON-parser.

nullSafeSet metode implementerer kortlægningen af ​​MyJson klasse ind i JSON-dokumentet. Ved at bruge Jackson-biblioteket kan du gøre det ved at bruge den samme ObjectMapper som i nullSafeGet metode.

@Override
public Object nullSafeGet(final ResultSet rs, final String[] names, final SessionImplementor session,
                          final Object owner) throws HibernateException, SQLException {
    final String cellContent = rs.getString(names[0]);
    if (cellContent == null) {
        return null;
    }
    try {
        final ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(cellContent.getBytes("UTF-8"), returnedClass());
    } catch (final Exception ex) {
        throw new RuntimeException("Failed to convert String to Invoice: " + ex.getMessage(), ex);
    }
}
 
@Override
public void nullSafeSet(final PreparedStatement ps, final Object value, final int idx,
                        final SessionImplementor session) throws HibernateException, SQLException {
    if (value == null) {
        ps.setNull(idx, Types.OTHER);
        return;
    }
    try {
        final ObjectMapper mapper = new ObjectMapper();
        final StringWriter w = new StringWriter();
        mapper.writeValue(w, value);
        w.flush();
        ps.setObject(idx, w.toString(), Types.OTHER);
    } catch (final Exception ex) {
        throw new RuntimeException("Failed to convert Invoice to String: " + ex.getMessage(), ex);
    }
}

En anden vigtig metode, du skal implementere, er deepCopy metode, der skal oprette en dyb kopi af en MyJson objekt. En af de nemmeste måder at gøre det på er at serialisere og deserialisere MyJson objekt. Dette tvinger JVM til at skabe en dyb kopi af objektet.

@Override
public Object deepCopy(final Object value) throws HibernateException {
    try {
        // use serialization to create a deep copy
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(value);
        oos.flush();
        oos.close();
        bos.close();
         
        ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
        Object obj = new ObjectInputStream(bais).readObject();
        bais.close();
        return obj;
    } catch (ClassNotFoundException | IOException ex) {
        throw new HibernateException(ex);
    }
}

Registrer UserType

I det næste trin skal du registrere din brugerdefinerede BrugerType . Du kan gøre dette med en @TypeDef annotation i package-info.java fil. Som du kan se i det følgende kodestykke, indstiller jeg navnet og typeClass egenskaben for @TypeDef annotation.

@org.hibernate.annotations.TypeDef(name = "MyJsonType", typeClass = MyJsonType.class)
 
package org.thoughts.on.java.model;

Dette forbinder UserType MyJsonType til navnet "MyJsonType ” som jeg så kan bruge med en @Type annotation i enhedstilknytningen.

@Entity
public class MyEntity {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;
 
    @Column
    @Type(type = "MyJsonType")
    private MyJson jsonProperty;
     
    ...
 
}

Og vi er næsten færdige. Hibernate vil nu bruge UserType MyJsonType for at bevare json-egenskaben attribut i databasen. Men der er stadig et skridt tilbage.

Dvaledialekt

Hibernates PostgreSQL-dialekt understøtter ikke JSONB datatype, og du skal registrere den. Det gør du ved at udvide en eksisterende dialekt og kalde registerColumnType metode i konstruktøren. Jeg bruger en PostgreSQL-database i dette eksempel og udvider Hibernates PostgreSQL94Dialect .

public class MyPostgreSQL94Dialect extends PostgreSQL94Dialect {
 
    public MyPostgreSQL94Dialect() {
        this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
    }
}

Nu kan du endelig gemme MyJson objekt i en JSONB kolonne.

Sådan bruger du en enhed med en JSONB kortlægning

Som du så i denne artikel, er de ting, du skal gøre for at knytte en enhedsattribut til en JSONB  kolonne afhænger af den Hibernate-version, du bruger. Men det er ikke tilfældet for din virksomhedskode, der bruger enheden eller dens attribut. Du kan bruge MyEntity enhed og dens MyJson attribut på samme måde som enhver anden enhed. Det giver dig også mulighed for at erstatte din UserType  implementering med Hibernates standardhåndtering, når du migrerer din applikation til Hibernate 6.

Følgende kodestykke viser et simpelt eksempel, der bruger EntityManager.find metode til at hente en enhed fra databasen og derefter ændre attributværdierne for MyJson objekt.

MyEntity e = em.find(MyEntity.class, 10000L);
e.getJsonProperty().setStringProp("changed");
e.getJsonProperty().setLongProp(789L);

Og hvis du vil vælge en enhed baseret på nogle egenskabsværdier inde i JSON-dokumentet, kan du bruge PostgreSQL's JSON-funktioner og -operatorer med en indbygget forespørgsel.

MyEntity e = (MyEntity) em.createNativeQuery("SELECT * FROM myentity e WHERE e.jsonproperty->'longProp' = '456'", MyEntity.class).getSingleResult();

Oversigt

PostgreSQL tilbyder forskellige proprietære datatyper, såsom JSONB type, jeg brugte i dette indlæg, til at gemme JSON-dokumenter i databasen.

Hibernate 6 giver en standard JSON-mapping. Du behøver kun at aktivere den ved at annotere din enhedsattribut med en @JdbcTypeCode annotation og indstil typen til SqlTypes.JSON .

Hibernate 4 og 5 understøtter ikke disse datatyper. Du skal selv implementere kortlægningen. Som du har set i dette indlæg, kan du gøre dette ved at implementere UserType interface, registrere den med en @TypeDef annotering og oprettelse af en Dvale-dialekt, der registrerer kolonnetypen.


Java tag