הייברנייט
הייברנייט לבדו אינו מספק תמיכה מלאה בחיפוש מלא על הטקסט. הוא צריך להסתמך על תמיכה של מנוע הבסיס של מסד הנתונים או פתרונות של זולתים.
הרחבה הנקראת חיפוש הייברנייט משתלבת עם אפפכייט לוסין או אלסטיק חיפוש (יש גם אינטגרציה עם OpenSearch).
פוסטגרס
לפוסטגרס הייתה תכונה של חיפוש מלא על הטקסט מגירסה 7.3. למרות שהוא לא יכול להתחרות עם מנועי חיפוש כמו אלסטיק חיפוש או לוסין, הוא עדיין מספק פתרון גמיש וחזק שעשוי להספיק לעמדת המשתמשים של היישומים – תכונות כמו שיפוע, דירוג ואינדקסציה.
נסביר בקצרה כיצד ניתן לבצע חיפוש מלא על הטקסט בפוסטגרס. למידע נוסף, אנא פנה לתיעוד של פוסטגרס. מבחינת ההתאמה הבסיסית של טקסט, החלק החשוב ביותר הוא מפעיל המתמטיקה @@
.
זה מחזיר true
אם המסמך (אובייקט מסוג tsvector
) תואם את השאילתה (אובייקט מסוג tsquery
).
הסדר אינו קריטי עבור המפעיל. אז, זה לא משנה אם אנו שמים את המסמך בצד שמאל של המפעיל ואת השאילתה בצד ימין או בסדר אחר.
להדגמה טובה יותר, אנו משתמשים בטבלת מסד הנתונים הנקראת tweet
.
create table tweet (
id bigint not null,
short_content varchar(255),
title varchar(255),
primary key (id)
)
עם נתונים כאלה:
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
עבור כל אחת מהרשומות.
SELECT id, to_tsvector('english', short_content) FROM tweet;
פלט:
הפלט מראה איך to_tsvector
המרת עמודת הטקסט לאובייקט tsvector
עבור הפרוטוקט חיפוש טקסטים 'english
'.
פרוטוקט חיפוש טקסטים
הפרמטר הראשון של פונקציית to_tsvector
שהועברה בדוגמה לעיל היה שם הפרוטוקט חיפוש טקסטים. במקרה זה, זה היה "english
". על פי מסמך פוסטגרס, פרוטוקט החיפוש הטקסטי הוא כדלקמן:
… פונקציונליות חיפוש טקסט מלא כוללת את היכולת לעשות דברים רבים יותר: לדלג על חידושיות מסוימות (מילים עצלה), לעבד מונחים שקולים, ולהשתמש בניתוח מתוחכם, למשל, לנתח בהתבסס על יותר מסתם רווחים. פונקציונליות זו נשלטת על ידי פרוטוקטים חיפוש טקסט.
אז, הצרוך הוא חלק קריטי בתהליך וחיוני לתוצאות החיפוש המלאות שלנו. עבור תצרוכים שונים, המנוע פוסטרגר יכול להחזיר תוצאות שונות. זה לא חייב להיות המצב בין מילון של שפות שונות. לדוגמה, אפשר לקבל שני תצרוכים לשפה אחת, אך אחד מתעלם משמות המכילים ספרות (לדוגמה, מספרים סדרתיים מסוימים). אם נשלח את השאילתה שלנו את המספר הסדרתי הספציפי שאנו מחפשים, שהוא חובה, לא נמצא שום רשומה עבור התצרוך שמתעלם ממילים עם מספרים. גם אם יש לנו רשומות כאלה במסד הנתונים, אנא בדוק את תיעוד התצרוך לקבלת מידע נוסף.
שאילתת טקסט
שאילתת טקסט תומכת בכמה פעולות כמו &
(AND), |
(OR), !
(NOT), ו-<->
(FOLLOWED BY). הפעולות השלושה הראשונים אינן דורשות הסבר מפורט. הפעולה <->
בודקת האם המילים קיימות והאם הן מופיעות בסדר מסוים. לכן, למשל, עבור השאילתה "rat <-> cat
", אנו מצפים שהמילה "cat" תתקיים, ואחריה "rat".
דוגמאות
- תוכן המכיל את rat ואת cat:
SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ to_tsquery('english', 'Rat & cat');
- תוכן המכיל את database ואת market, וה-market הוא המילה השלישית אחרי מסד נתונים:
SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ to_tsquery('english', 'database <3> market');
- תוכן המכיל את database אך לא את Postgres:
SELECT t.id, t.short_content FROM tweet t WHERE to_tsvector('english', t.short_content) @@ to_tsquery('english', 'database & !Postgres');
- תוכן המכיל Postgres או Oracle:
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')
.
לשימוש הבא:
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')
.
לשימוש הבא:
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')
.
לשימוש הבא:
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
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
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 לפונקציות JSON של PostgreSQL וחיפוש שלמים.
לפרויקט ה-Maven, עלינו להוסיף את ה-תלבושות הבאות:
<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
, עלינו לרשום אותם במסד הנתונים של היג'יניבר.
זה אומר שחייב להיות ממשק 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
, שניתן לעשות זאת באמצעות תכנות. כדי לראות איך לעשות זאת, בדוק את ה-קישור הזה.
כעת, ניתן להשתמש במפעילי חיפוש מלא בשאילתות של היג'יניבר.
PlainToTSQueryFunction
זהו רכיב העוטף את הפונקציה plainto_tsquery.
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'
, הקוד יפיק את הצהרת הבאה:
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.
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'
, הקוד יוצר את הצהרה הבאה:
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.
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'
, הקוד יוצר את הצהרה הבאה:
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 עשויה להיות קלה, יישום פרדיקטים יהיה פרקטיקה טובה יותר, במיוחד כשעליך לטפל בקריטריונים לחיפוש המבוססים על תכונות דינמיות מה-API שלך.
מסקנה
כפי שצוין במאמר הקודם, תמיכת חיפוש מלא-טקסט ב-Postgres יכולה להוות אלטרנטיבה טובה למנועי חיפוש משמעותיים כמו Elasticsearch או Lucene, במקרים מסוימים. זה יכול לחסוך לנו את ההחלטה להוסיף פתרונות של שלישיות לעמדת הטכנולוגיה שלנו, שיכולה גם להוסיף מורכבות נוספת ועלויות נוספות.
Source:
https://dzone.com/articles/postgres-full-text-search-with-hibernate-6