بحث النص الكامل في Postgres مع Hibernate 6

هيبرنيت

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

تم توفير تمديد يسمى بحث هيبرنيت يربط مع أباتشي لوسن أو بحث الكتان (هناك أيضا تكامل مع OpenSearch).

بوستغرس

بوستغرس كان لديه وظيفة البحث الكامل النصي منذ الإصدار 7.3. على الرغم من أنه لا يمكن التنافس مع محركات البحث مثل بحث الكتان أو لوسن ، إلا أنه لا يزال يوفر حلاً مرنًا وقويًا قد يكفي لتلبية التوقعات الخاصة بمستخدمي التطبيقات – ميزات مثل التبديل الجذري والترتيب والفهرسة.

سنشرح باختصار كيف يمكننا القيام ببحث كامل النص في بوستغرس. لمزيد من المعلومات ، يرجى زيارة توثيق بوستغرس. بالنسبة للمباراة النصية الأساسية ، الجزء الأكثر أهمية هو مشغل الرياضيات @@.

ترجع true إذا كان الوثيقة (كائن من النوع tsvector) تطابق الاستعلام (كائن من النوع tsquery).

الترتيب ليس أمراً حاسماً بالنسبة للمشغل. لذا ، لا يهم إذا وضعنا الوثيقة على الجانب الأيسر من المشغل والاستعلام على الجانب الأيمن أو بترتيب مختلف.

لشرح أفضل، نستخدم جدول قاعدة البيانات يسمى tweet.

SQL

 

create table tweet (
        id bigint not null,
        short_content varchar(255),
        title varchar(255),
        primary key (id)
    )

مع مثل هذه البيانات:

SQL

 

INSERT INTO tweet (id, title, short_content) VALUES (1, 'Cats', 'Cats rules the world');
INSERT INTO tweet (id, title, short_content) VALUES (2, 'Rats', 'Rats rules in the sewers');
INSERT INTO tweet (id, title, short_content) VALUES (3, 'Rats vs Cats', 'Rats and Cats hates each other');

INSERT INTO tweet (id, title, short_content) VALUES (4, 'Feature', 'This project is design to wrap already existed functions of Postgres');
INSERT INTO tweet (id, title, short_content) VALUES (5, 'Postgres database', 'Postgres is one of the widly used database on the market');
INSERT INTO tweet (id, title, short_content) VALUES (6, 'Database', 'On the market there is a lot of database that have similar features like Oracle');

الآن دعونا نرى كيف يبدو كائن tsvector لعمود short_content لكل من السجلات.

SQL

 

SELECT id, to_tsvector('english', short_content) FROM tweet;

الناتج:

يوضح الناتج كيف يحول to_tsvector عمود النص إلى كائن tsvector لتكوين البحث النصي ‘english‘.

تكوين البحث النصي

كان المعامل الأول الممرر لدالة to_tsvector في المثال أعلاه هو اسم تكوين البحث النصي. في تلك الحالة، كان “english“. وفقًا لوثائق Postgres، يتم تعريف تكوين البحث النصي على النحو التالي:

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

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

استعلام النص

استعلام النص يدعم مثل هذه المشغلين مثل & (AND)، | (OR)، ! (NOT)، و <-> (FOLLOWED BY). لا تتطلب المشغلات الثلاثة الأولى توضيحاً أكثر. يتحقق المشغل <-> مما إذا كانت الكلمات موجودة وإذا كانت موضوعة في ترتيب محدد. لذا، على سبيل المثال، لاستعلام “rat <-> cat“، نتوقع أن تكون كلمة “cat” موجودة، تليها كلمة “rat”.

أمثلة

  • محتوى يحتوي على rat وcat:
SQL

 

SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ to_tsquery('english', 'Rat & cat');

  • محتوى يحتوي على database وmarket, وأن السوق هو الكلمة الثالثة بعد قاعدة البيانات:
SQL

 

SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ to_tsquery('english', 'database <3> market');

  • محتوى يحتوي على database ولكن ليس Postgres:
SQL

 

SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ to_tsquery('english', 'database & !Postgres');

  • المحتوى الذي يحتوي على Postgres أو Oracle:
SQL

 

SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ to_tsquery('english', 'Postgres | Oracle');

وظائف التغليف

ذكرت إحدى وظائف التغليف التي تنشئ استعلامات نصية في هذا المقال مسبقًا ، وهي to_tsquery. هناك المزيد من هذه الوظائف مثل:

  • plainto_tsquery
  • phraseto_tsquery
  • websearch_to_tsquery

plainto_tsquery

تحول plainto_tsquery جميع الكلمات المرسلة إلى استعلام حيث تتم دمج جميع الكلمات باستخدام عامل التشغيل & (AND). على سبيل المثال ، المكافئ لـ plainto_tsquery('english', 'Rat cat') هو to_tsquery('english', 'Rat & cat').

للاستخدام التالي:

SQL

 

SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ plainto_tsquery('english', 'Rat cat');

نحصل على النتيجة التالية:

phraseto_tsquery

تحول phraseto_tsquery جميع الكلمات المرسلة إلى استعلام حيث تتم دمج جميع الكلمات باستخدام عامل التشغيل <-> (FOLLOW BY). على سبيل المثال ، المكافئ لـ phraseto_tsquery('english', 'cat rule') هو to_tsquery('english', 'cat <-> rule').

للاستخدام التالي:

SQL

 

SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ phraseto_tsquery('english', 'cat rule');

نحصل على النتيجة التالية:

websearch_to_tsquery

يستخدم websearch_to_tsquery بناء جملة متنوع لإنشاء استعلام نص صالح.

  • نص غير مُقتبس: يحول جزءًا من البناء النحوي بنفس طريقة plainto_tsquery
  • نص مُقتبس: يحول جزءًا من البناء النحوي بنفس طريقة phraseto_tsquery
  • أو: يحول إلى “|” (أو) المشغل
  • -“: يعادل “!” (لا) المشغل

على سبيل المثال، المكافئ لـ websearch_to_tsquery('english', '"cat rule" or database -Postgres') هو to_tsquery('english', 'cat <-> rule | database & !Postgres').

للاستخدام التالي:

SQL

 

SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ websearch_to_tsquery('english', '"cat rule" or database -Postgres');

نحصل على النتيجة التالية:

دعم Postgres و Hibernate الأصلي

كما ذكر في المقال، لا يمتلك Hibernate دعمًا للبحث النصي الكامل. يجب أن يعتمد على دعم محرك قاعدة البيانات. مما يعني أنه يُسمح لنا بتنفيذ استعلامات SQL أصلية كما هو موضح في الأمثلة التالية:

  • plainto_tsquery
Java

 

public List<Tweet> findBySinglePlainQueryInDescriptionForConfigurationWithNativeSQL(String textQuery, String configuration) {
        return entityManager.createNativeQuery(String.format("select * from tweet t1_0 where to_tsvector('%1$s', t1_0.short_content) @@ plainto_tsquery('%1$s', :textQuery)", configuration), Tweet.class).setParameter("textQuery", textQuery).getResultList();
    }

  • websearch_to_tsquery
Java

 

public List<Tweet> findCorrectTweetsByWebSearchToTSQueryInDescriptionWithNativeSQL(String textQuery, String configuration) {
        return entityManager.createNativeQuery(String.format("select * from tweet t1_0 where to_tsvector('%1$s', t1_0.short_content) @@ websearch_to_tsquery('%1$s', :textQuery)", configuration), Tweet.class).setParameter("textQuery", textQuery).getResultList();
    }

Hibernate مع مكتبة posjsonhelper

مكتبة posjsonhelper هي مشروع مفتوح المصدر يضيف الدعم لاستعلامات Hibernate لـ وظائف PostgreSQL JSON والبحث النصي الكامل.

لمشروع Maven، نحتاج لإضافة المراحل التبعيات التالية:

XML

 

<dependency>
    <groupId>com.github.starnowski.posjsonhelper.text</groupId>
    <artifactId>hibernate6-text</artifactId>
    <version>0.3.0</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.4.0.Final</version>
</dependency>

لاستخدام المكونات الموجودة في مكتبة posjsonhelper، نحتاج لتسجيلها في سياق Hibernate.

هذا يعني أنه يجب أن يكون هناك تنفيذ محدد لـ org.hibernate.boot.model.FunctionContributor. المكتبة لديها تنفيذ لهذه الواجهة، وهو com.github.starnowski.posjsonhelper.hibernate6.PosjsonhelperFunctionContributor.

A file with the name “org.hibernate.boot.model.FunctionContributor” under the “resources/META-INF/services” directory is required to use this implementation.

هناك طريقة أخرى لتسجيل مكونات posjsonhelper، والتي يمكن القيام بها من خلال البرمجة. لمعرفة كيفية القيام بذلك، تحقق من هذا الرابط.

الآن، يمكننا استخدام عوامل البحث النصي الكامل في استعلامات Hibernate.

PlainToTSQueryFunction

هذا مكون يغلف وظيفة plainto_tsquery.

Java

 

public List<Tweet> findBySinglePlainQueryInDescriptionForConfiguration(String textQuery, String configuration) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tweet> query = cb.createQuery(Tweet.class);
        Root<Tweet> root = query.from(Tweet.class);
        query.select(root);
        query.where(new TextOperatorFunction((NodeBuilder) cb, new TSVectorFunction(root.get("shortContent"), configuration, (NodeBuilder) cb), new PlainToTSQueryFunction((NodeBuilder) cb, configuration, textQuery), hibernateContext));
        return entityManager.createQuery(query).getResultList();
    }

للتكوين بالقيمة 'english'، سيولد الكود البيان التالي:

Java

 

select
        t1_0.id,
        t1_0.short_content,
        t1_0.title 
    from
        tweet t1_0 
    where
        to_tsvector('english', t1_0.short_content) @@ plainto_tsquery('english', ?);

PhraseToTSQueryFunction

يُغلِفُ هذا المكوِّنُ وظيفة phraseto_tsquery.

Java

 

public List<Tweet> findBySinglePhraseInDescriptionForConfiguration(String textQuery, String configuration) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tweet> query = cb.createQuery(Tweet.class);
        Root<Tweet> root = query.from(Tweet.class);
        query.select(root);
        query.where(new TextOperatorFunction((NodeBuilder) cb, new TSVectorFunction(root.get("shortContent"), configuration, (NodeBuilder) cb), new PhraseToTSQueryFunction((NodeBuilder) cb, configuration, textQuery), hibernateContext));
        return entityManager.createQuery(query).getResultList();
        }

للتكيّفِ بالقيمة 'english'، سيولد الكود البيان التالي:

SQL

 

select
        t1_0.id,
        t1_0.short_content,
        t1_0.title 
    from
        tweet t1_0 
    where
        to_tsvector('english', t1_0.short_content) @@ phraseto_tsquery('english', ?)

WebsearchToTSQueryFunction

يُغلِفُ هذا المكوِّنُ وظيفة websearch_to_tsquery.

Java

 

public List<Tweet> findCorrectTweetsByWebSearchToTSQueryInDescription(String phrase, String configuration) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tweet> query = cb.createQuery(Tweet.class);
        Root<Tweet> root = query.from(Tweet.class);
        query.select(root);
        query.where(new TextOperatorFunction((NodeBuilder) cb, new TSVectorFunction(root.get("shortContent"), configuration, (NodeBuilder) cb), new WebsearchToTSQueryFunction((NodeBuilder) cb, configuration, phrase), hibernateContext));
        return entityManager.createQuery(query).getResultList();
    }

للتكيّفِ بالقيمة 'english'، سيولد الكود البيان التالي:

SQL

 

select
        t1_0.id,
        t1_0.short_content,
        t1_0.title 
    from
        tweet t1_0 
    where
        to_tsvector('english', t1_0.short_content) @@ websearch_to_tsquery('english', ?)

استعلامات HQL

يمكن استخدام جميع المكوِّنات المذكورة في استعلامات HQL. للتحقق من كيفية القيام بذلك، يرجى النقر فوق هذا الرابط.

لماذا نستخدم مكتبة posjsonhelper عندما يمكننا استخدام الطريقة الأصلية مع Hibernate؟

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

الخاتمة

كما ذُكرَ في المقالة السابقة، يمكن أن يكون دعم بوستغريس للبحث الكامل النصي خيارًا جيدًا لأجهزة البحث الهامة مثل Elasticsearch أو Lucene، في بعض الحالات. يُمكن أن يوفر لنا من اتخاذ قرار لإضافة حلول ثالثة لمجموعة تكنولوجياتنا، والتي قد تضيف مزيدًا من التعقيد والتكاليف الإضافية.

Source:
https://dzone.com/articles/postgres-full-text-search-with-hibernate-6