Flazzo memiliki fokus utama untuk menambah nilai bisnis Anda.

Blog

Postgres JSON berfungsi dengan Hibernate 5

16891507-thumb.jpg
Blog

Postgres JSON berfungsi dengan Hibernate 5


Database Postgres mendukung beberapa tipe JSON dan operasi khusus untuk orang-orang ini.

Dalam beberapa kasus, operasi ini bisa menjadi alternatif yang baik untuk database dokumen seperti MongoDB atau database NoSQL lainnya. Tentu saja, database seperti MongoDB mungkin memiliki proses replikasi yang lebih baik, tetapi topik tersebut berada di luar cakupan artikel ini.

Pada artikel ini, kita akan fokus menggunakan operasi JSON dalam proyek yang menggunakan bingkai hibernasi dengan versi 5.

Contoh model

Model kami terlihat seperti contoh di bawah ini:

@Entity
@Table(name = "item")
public class Item {

    @Id
    private Long id;

    @Column(name = "jsonb_content", columnDefinition = "jsonb")
    private String jsonbContent;

    public Long getId() {
        return id;
    }

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

    public String getJsonbContent() {
        return jsonbContent;
    }

    public void setJsonbContent(String jsonbContent) {
        this.jsonbContent = jsonbContent;
    }
}

Penting!: Kita bisa menggunakan tipe JSON tertentu untuk jsonbContent properti, tetapi dalam Hibernasi versi 5 ini tidak akan memberikan manfaat dari sudut pandang operasi.

Operasi DDL:

create table item (
       id int8 not null,
        jsonb_content jsonb,
        primary key (id)
    )

Untuk tujuan presentasi, mari kita asumsikan bahwa basis data kita berisi catatan seperti itu:

INSERT INTO item (id, jsonb_content) VALUES (1, '{"top_element_with_set_of_values":["TAG1","TAG2","TAG11","TAG12","TAG21","TAG22"]}');
INSERT INTO item (id, jsonb_content) VALUES (2, '{"top_element_with_set_of_values":["TAG3"]}');
INSERT INTO item (id, jsonb_content) VALUES (3, '{"top_element_with_set_of_values":["TAG1","TAG3"]}');
INSERT INTO item (id, jsonb_content) VALUES (4, '{"top_element_with_set_of_values":["TAG22","TAG21"]}');
INSERT INTO item (id, jsonb_content) VALUES (5, '{"top_element_with_set_of_values":["TAG31","TAG32"]}');

-- item without any properties, just an empty json
INSERT INTO item (id, jsonb_content) VALUES (6, '{}');

-- int values
INSERT INTO item (id, jsonb_content) VALUES (7, '{"integer_value": 132}');
INSERT INTO item (id, jsonb_content) VALUES (8, '{"integer_value": 562}');
INSERT INTO item (id, jsonb_content) VALUES (9, '{"integer_value": 1322}');

-- double values
INSERT INTO item (id, jsonb_content) VALUES (10, '{"double_value": 353.01}');
INSERT INTO item (id, jsonb_content) VALUES (11, '{"double_value": -1137.98}');
INSERT INTO item (id, jsonb_content) VALUES (12, '{"double_value": 20490.04}');

-- enum values
INSERT INTO item (id, jsonb_content) VALUES (13, '{"enum_value": "SUPER"}');
INSERT INTO item (id, jsonb_content) VALUES (14, '{"enum_value": "USER"}');
INSERT INTO item (id, jsonb_content) VALUES (15, '{"enum_value": "ANONYMOUS"}');

-- string values
INSERT INTO item (id, jsonb_content) VALUES (16, '{"string_value": "this is full sentence"}');
INSERT INTO item (id, jsonb_content) VALUES (17, '{"string_value": "this is part of sentence"}');
INSERT INTO item (id, jsonb_content) VALUES (18, '{"string_value": "the end of records"}');

-- inner elements
INSERT INTO item (id, jsonb_content) VALUES (19, '{"child": {"pets" : ["dog"]}}');
INSERT INTO item (id, jsonb_content) VALUES (20, '{"child": {"pets" : ["cat"]}}');
INSERT INTO item (id, jsonb_content) VALUES (21, '{"child": {"pets" : ["dog", "cat"]}}');
INSERT INTO item (id, jsonb_content) VALUES (22, '{"child": {"pets" : ["hamster"]}}');

Pendekatan kueri asli

Di Hibernate 5 kita bisa menggunakan pendekatan asli di mana kita menjalankan perintah SQL langsung.

Penting!: Tolong, untuk tujuan presentasi, abaikan fakta bahwa kode di bawah memungkinkan injeksi SQL untuk ekspresi dari LIKE operator. Tentu saja, untuk tindakan seperti itu kita harus menggunakan parameter dan PreparedStatement.


private EntityManager entityManager;

public List<Item> findAllByStringValueAndLikeOperatorWithNativeQuery(String expression) {
        return entityManager.createNativeQuery("SELECT * FROM item i WHERE i.jsonb_content#>>'{string_value}' LIKE '" + expression + "'", Item.class).getResultList();
    }

Pada contoh di atas, terdapat penggunaan the #>> operator yang mengekstrak sub-objek JSON di jalur yang ditentukan sebagai teks (silakan centang file Dokumentasi Postgre untuk lebih jelasnya).

Dalam kebanyakan kasus, kueri seperti itu (tentu saja, dengan nilai yang diloloskan) sudah cukup. Namun, jika kita perlu menerapkan pembuatan semacam kueri dinamis berdasarkan parameter yang diteruskan ke API kita, sebaiknya gunakan semacam pembuat kriteria.

Posjsonhelper

Hibernasi 5 secara default tidak mendukung fungsi Postgres JSON. Untungnya, Anda dapat menerapkannya sendiri atau menggunakan posjsonhelper library yang merupakan proyek sumber terbuka.

Proyek ada di repositori pusat Maven, sehingga Anda dapat dengan mudah menambahkannya dengan menambahkannya sebagai ketergantungan pada proyek Maven Anda.

        <dependency>
            <groupId>com.github.starnowski.posjsonhelper</groupId>
            <artifactId>hibernate5</artifactId>
            <version>0.1.0</version>
        </dependency>

Untuk menggunakan posjsonhelper perpustakaan di proyek Anda, Anda harus menggunakan Postgres dialek diimplementasikan dalam proyek. Misalnya:

com.github.starnowski.posjsonhelper.hibernate5.dialects.PostgreSQL95DialectWrapper ...

Jika proyek Anda sudah memiliki kelas dialek khusus, ada juga opsi untuk menggunakan:

com.github.starnowski.posjsonhelper.hibernate5.PostgreSQLDialectEnricher;

Menggunakan Komponen Kriteria

Contoh di bawah memiliki perilaku yang mirip dengan contoh sebelumnya yang menggunakan kueri asli. Namun, dalam hal ini, kita akan menggunakan generator kriteria.

	private EntityManager entityManager;

    public List<Item> findAllByStringValueAndLikeOperator(String expression) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Item> query = cb.createQuery(Item.class);
        Root<Item> root = query.from(Item.class);
        query.select(root);
        query.where(cb.like(new JsonBExtractPathText((CriteriaBuilderImpl) cb, singletonList("string_value"), root.get("jsonbContent")), expression));
        return entityManager.createQuery(query).getResultList();
    }

Hibernasi akan menghasilkan kode SQL seperti di bawah ini:

select
            item0_.id as id1_0_,
            item0_.jsonb_content as jsonb_co2_0_ 
        from
            item item0_ 
        where
            jsonb_extract_path_text(item0_.jsonb_content,?) like ?

ITU jsonb_extract_path_text adalah fungsi Postgres yang setara dengan #>> operator (silakan lihat dokumentasi Postgres yang ditautkan sebelumnya untuk detailnya).

Operasi larik

Pustaka ini mendukung beberapa operator fungsi JSON Postgres seperti:

  • ?&– Memeriksa apakah semua string larik teks ada sebagai kunci tingkat atas atau elemen larik. Jadi biasanya jika kita memiliki properti JSON yang berisi sebuah array, Anda dapat memeriksa apakah itu berisi semua elemen yang Anda cari.
  • ?| – Memeriksa apakah ada string larik teks sebagai kunci tingkat atas atau elemen larik. Jadi biasanya jika kita memiliki properti JSON yang berisi array, Anda dapat memeriksa apakah itu berisi setidaknya beberapa elemen yang Anda cari.

Perubahan DDL yang Diperlukan

Operator di atas tidak dapat digunakan di HQL karena karakter khusus. Itu sebabnya kita perlu membungkusnya, misalnya, dalam fungsi SQL kustom. Posjsonhelper Pustaka memerlukan dua fungsi SQL khusus yang akan menggabungkan operator ini. Untuk pengaturan default, fungsi-fungsi ini akan memiliki implementasi di bawah ini.

CREATE OR REPLACE FUNCTION jsonb_all_array_strings_exist(jsonb, text[]) RETURNS boolean AS $$
SELECT $1 ?& $2;
$$ LANGUAGE SQL;

CREATE OR REPLACE FUNCTION jsonb_any_array_strings_exist(jsonb, text[]) RETURNS boolean AS $$
SELECT $1 ?| $2;
$$ LANGUAGE SQL;

Untuk informasi selengkapnya tentang cara menyesuaikan atau menambahkan DDL yang diperlukan secara terprogram, silakan lihat “Terapkan perubahan DDL.”

Amplop “?&”

Contoh kode di bawah ini menunjukkan cara membuat kueri yang memeriksa rekaman di mana properti JSON yang berisi larik berisi semua elemen string yang kita cari.

    
	private EntityManager entityManager;

	public List<Item> findAllByAllMatchingTags(Set<String> tags) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Item> query = cb.createQuery(Item.class);
        Root<Item> root = query.from(Item.class);
        query.select(root);
        query.where(new JsonbAllArrayStringsExistPredicate(hibernateContext, (CriteriaBuilderImpl) cb, new JsonBExtractPath((CriteriaBuilderImpl) cb, singletonList("top_element_with_set_of_values"), root.get("jsonbContent")), tags.toArray(new String[0])));
        return entityManager.createQuery(query).getResultList();
    }

Jika tag berisi dua elemen, maka Hibernate akan menghasilkan SQL di bawah ini:

select
            item0_.id as id1_0_,
            item0_.jsonb_content as jsonb_co2_0_ 
        from
            item item0_ 
        where
            jsonb_all_array_strings_exist(jsonb_extract_path(item0_.jsonb_content,?), array[?,?])=true

“?|” Kemasan

Kode contoh di bawah ini menunjukkan cara membuat kueri yang memeriksa catatan yang properti JSON yang berisi larik memiliki setidaknya satu elemen string yang kita telusuri.

	private EntityManager entityManager;
    public List<Item> findAllByAnyMatchingTags(HashSet<String> tags) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Item> query = cb.createQuery(Item.class);
        Root<Item> root = query.from(Item.class);
        query.select(root);
        query.where(new JsonbAnyArrayStringsExistPredicate(hibernateContext, (CriteriaBuilderImpl) cb, new JsonBExtractPath((CriteriaBuilderImpl) cb, singletonList("top_element_with_set_of_values"), root.get("jsonbContent")), tags.toArray(new String[0])));
        return entityManager.createQuery(query).getResultList();
    }

Jika tag berisi dua elemen, Hibernate akan menghasilkan SQL di bawah ini:

select
            item0_.id as id1_0_,
            item0_.jsonb_content as jsonb_co2_0_ 
        from
            item item0_ 
        where
            jsonb_any_array_strings_exist(jsonb_extract_path(item0_.jsonb_content,?), array[?,?])=true

Untuk lebih banyak contoh penggunaan operator numerik, silakan lihat demo objek cad Dan pengujian cad.

Kesimpulan

Dalam beberapa kasus, tipe dan fungsi Postgres JSON dapat menjadi alternatif yang baik untuk database NoSQL. Hal ini dapat menghemat keputusan kami untuk menambahkan solusi NoSQL ke tumpukan teknologi kami, yang juga dapat menambah kerumitan dan biaya tambahan.