وظائف Postgres JSON مع Hibernate 5

قاعدة بيانات Postgres تدعم عدد قليل من أنواع JSON وعمليات خاصة لتلك الأنواع.

في بعض الحالات، قد تكون تلك العمليات بديلًا جيدًا لقواعد المستندات مثل MongoDB أو قواعد NoSQL أخرى. بالطبع، قد تمتلك قواعد البيانات مثل MongoDB عمليات تكرار أفضل، لكن هذا الموضوع خارج نطاق هذا المقال.

في هذا المقال، سنركز على كيفية استخدام عمليات JSON في المشاريع التي تستخدم إطار العمل Hibernate بالإصدار 5.

مثال للنموذج

يبدو نموذجنا كما في المثال التالي:

Java

 

@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;
    }
}

مهم!: كان بإمكاننا استخدام نوع JSON محدد للخاصية jsonbContent، لكن في إصدار 5 من Hibernate، لن يحقق ذلك أي منفعة من وجهة نظر العمليات.

عملية DDL:

SQL

 

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

لأغراض العرض، لنفترض أن قاعدة بياناتنا تحتوي على مثل هذه السجلات:

SQL

 

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"]}');

-- عنصر بدون أي خصائص، مجرد جدول بيانات خالٍ
INSERT INTO item (id, jsonb_content) VALUES (6, '{}');

-- قيم عددية
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}');

-- قيم عشرية
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}');

-- قيم تعدادية
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"}');

-- قيم نصية
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"}');

-- عناصر داخلية
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"]}}');

طريقة الاستعلام الأصلي

في Hibernate 5، يمكننا استخدام نهج أصلي حيث نقوم بتنفيذ أمر SQL مباشر.

مهم!: من فضلك، لأغراض العرض، استبعد حقيقة أن الكود أدناه يسمح بتعبئة SQL للعملية LIKE. بالطبع، لمثل هذا العمل، يجب استخدام المعاملات و PreparedStatement.

Java

 


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();
    }

في المثال أعلاه، يوجد استخدام لمشغل #>> الذي يستخرج الكائن JSON الفرعي عند المسار المحدد كنص (يرجى التحقق من وثائق Postgres للمزيد من التفاصيل).

في معظم الحالات، مثل هذا الاستعلام (بالطبع، بقيمة معطلة) يكفي. ومع ذلك، إذا احتجنا لتنفيذ إنشاء نوع من الاستعلام الديناميكي على أساس المعاملات التي تم تمريرها في API الخاص بنا، سيكون من الأفضل استخدام بناء معايير من نوع ما.

Posjsonhelper

Hibernate 5 بشكل افتراضي لا يدعم وظائف Postgres JSON. لحسن الحظ، يمكنك تنفيذها بنفسك أو استخدام مكتبة posjsonhelper التي هي مشروع مفتوح المصدر.

المشروع موجود في مستودع Maven المركزي، لذا يمكنك إضافته بسهولة عن طريق إضافته كعنصر تبعية في مشروع Maven الخاص بك.

XML

 

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

لاستخدام مكتبة posjsonhelper في مشروعك، تحتاج إلى استخدام لهجة Postgres التي تم تنفيذها في المشروع. على سبيل المثال:

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

في حال كان لديك مشروع بالفعل فئة لهجة خاصة، فهناك أيضًا احتمال استخدام:

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

استخدام مكونات المعايير

المثال التالي له سلوك مماثل للمثال السابق الذي استخدم تعليمة الاستعلام الأصلية. ومع ذلك، في هذه الحالة، سنستخدم بناء المعايير.

Java

 

	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();
    }

سيولد Hibernate الشفرة ال SQL كما يلي:

SQL

 

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 ?

الدالة jsonb_extract_path_text هي دالة Postgres تعادل المشغل #>> (يرجى الاطلاع على وثائق Postgres المرتبطة في وقت سابق للحصول على المزيد من التفاصيل).

العمليات على المصفوفات

المكتبة تدعم عدد قليل من عوامل وظائف JSON Postgres مثل:

  • ?& – يتحقق مما إذا كانت جميع السلاسل في مصفوفة النص موجودة كمفاتيح أولية أو عناصر مصفوفة. بشكل عام إذا كان لدينا خاصية JSON تحتوي على مصفوفة ثم يمكنك التحقق مما إذا كانت تحتوي على جميع العناصر التي تبحث عنها.
  • ?| – يتحقق مما إذا كان أيًا من السلاسل في مصفوفة النص موجودًا كمفاتيح أولية أو عناصر مصفوفة. بشكل عام إذا كان لدينا خاصية JSON تحتوي على مصفوفة ثم يمكنك التحقق مما إذا كانت تحتوي على عنصر واحد على الأقل مما تبحث عنه.

التغييرات المطلوبة في DDL

لا يمكن استخدام المشغل أعلاه في HQL بسبب الأحرف الخاصة. لهذا السبب نحتاج إلى تضمينهم، على سبيل المثال، داخل دالة SQL مخصصة. تتطلب مكتبة Posjsonhelper دالتي SQL مخصصتين تلتف هذه المشغلات. بالنسبة للإعداد الافتراضي، سيكون لهذه الدالات التنفيذ التالي.

PLSQL

 

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;

لمزيد من المعلومات حول كيفية تخصيص أو إضافة برمجيًا التغييرات المطلوبة في DDL، يرجى الاطلاع على القسم “تطبيق تغييرات DDL.”

“?&” الملفق

المثال التعليمات البرمجية التالي يوضح كيفية إنشاء عملية الاستعلام التي تبحث في السجلات التي تحتوي على خاصية JSON تحتوي على مجموعة من العناصر التي نبحث عنها.

Java

 

    
	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();
    }

في حالة وجود عناصر مختلفتين داخل العلامات، سيولد Hibernate الSQL التالي:

SQL

 

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

“؟|” الملفق

المثال التعليمات البرمجية التالي يوضح كيفية إنشاء عملية الاستعلام التي تبحث في السجلات التي تحتوي على خاصية JSON تحتوي على مجموعة من العناصر التي نبحث عنها على الأقل.

Java

 

	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();
    }

في حالة وجود عناصر مختلفتين داخل العلامات، سيولد Hibernate الSQL التالي:

SQL

 

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

للحصول على المزيد من الأمثلة حول كيفية استخدام عوامل العددية يرجى الاطلاع على العرض التوضيحي لكائن المطور و اختبارات المطور.

الخاتمة

في بعض الحالات، يمكن لأنواع ووظائف JSON في Postgres أن تكون بديلاً جيداً لقواعد البيانات NoSQL. قد يساعدنا هذا على تجنب اتخاذ قرار إضافة حلول NoSQL إلى سلسلة تكنولوجياتنا التي قد تضيف مزيدًا من التعقيد والتكاليف الإضافية.

Source:
https://dzone.com/articles/postgres-json-functions-with-hibernate-5