Postgres-Datenbank unterstützt einige JSON-Typen und spezielle Operationen für diese Typen.
In einigen Fällen können diese Operationen eine gute Alternative zu Dokumentendatenbanken wie MongoDB oder anderen NoSQL-Datenbanken sein. Natürlich könnten Datenbanken wie MongoDB bessere Replikationsprozesse haben, aber dieses Thema liegt außerhalb des Umfangs dieses Artikels.
In diesem Artikel konzentrieren wir uns darauf, wie man JSON-Operationen in Projekten verwendet, die auf dem Hibernate-Framework in Version 5 basieren.
Beispielmodell
Unser Modell sieht wie im folgenden Beispiel aus:
@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;
}
}
Wichtig!: Wir könnten einen spezifischen JSON-Typ für die Eigenschaft jsonbContent
verwenden, aber in Hibernate-Version 5 würde das aus Sicht der Operationen keinen Vorteil bringen.
DDL-Operation:
create table item (
id int8 not null,
jsonb_content jsonb,
primary key (id)
)
Zur Demonstration nehmen wir an, dass unsere Datenbank solche Datensätze enthält:
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"]}');
-- Artikel ohne Eigenschaften, nur ein leeres JSON
INSERT INTO item (id, jsonb_content) VALUES (6, '{}');
-- Ganzzahlwerte
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}');
-- Gleitkommawerte
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}');
-- Enumerationswerte
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"}');
-- Zeichenkettenwerte
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"}');
-- Innerelemente
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"]}}');
Native Query-Ansatz
In Hibernate 5 können wir einen nativen Ansatz verwenden, bei dem wir einen direkten SQL-Befehl ausführen.
Wichtig!: Bitte, für Präsentationszwecke, lassen Sie die Tatsache weg, dass der unten stehende Code eine SQL-Injection für den Ausdruck für den LIKE
-Operator zulässt. Natürlich sollten wir für solche Aktionen Parameter und PreparedStatement
verwenden.
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();
}
Im obigen Beispiel wird der #>>
-Operator verwendet, der das JSON-Unterobjekt am angegebenen Pfad als Text extrahiert (bitte die Postgres-Dokumentation für weitere Details überprüfen).
In den meisten Fällen ist eine solche Abfrage (natürlich mit einem escapeten Wert) ausreichend. Wenn wir jedoch eine Art dynamische Abfrage erstellen müssen, die auf Parametern basiert, die von unserer API übergeben werden, wäre es besser, eine Art Kriterien-Builder zu verwenden.
Posjsonhelper
Hibernate 5 unterstützt standardmäßig keine Postgres JSON-Funktionen. Glücklicherweise können Sie dies selbst implementieren oder die posjsonhelper -Bibliothek verwenden, die ein Open-Source-Projekt ist.
Das Projekt ist im Maven Central Repository verfügbar, sodass Sie es einfach hinzufügen können, indem Sie es als Abhängigkeit zu Ihrem Maven-Projekt hinzufügen.
<dependency>
<groupId>com.github.starnowski.posjsonhelper</groupId>
<artifactId>hibernate5</artifactId>
<version>0.1.0</version>
</dependency>
Um die posjsonhelper
-Bibliothek in Ihrem Projekt zu verwenden, müssen Sie den Postgres-Dialekt, der im Projekt implementiert ist, verwenden. Zum Beispiel:
com.github.starnowski.posjsonhelper.hibernate5.dialects.PostgreSQL95DialectWrapper ...
Falls Ihr Projekt bereits eine benutzerdefinierte Dialekt-Klasse hat, besteht auch die Möglichkeit, Folgendes zu verwenden:
com.github.starnowski.posjsonhelper.hibernate5.PostgreSQLDialectEnricher;
Verwendung von Kriterienkomponenten
Das folgende Beispiel verhält sich ähnlich wie das vorherige Beispiel, das eine native Abfrage verwendete. In diesem Fall werden wir jedoch einen Kriterien-Builder verwenden.
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 generiert den SQL-Code wie folgt:
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 ?
Die jsonb_extract_path_text
ist eine Postgres-Funktion, die dem #>>
-Operator entspricht (bitte prüfen Sie die zuvor verlinkte Postgres-Dokumentation für weitere Details).
Operationen auf Arrays
Die Bibliothek unterstützt einige Postgres JSON-Funktionsoperatoren wie:
?&
– Prüft, ob alle Zeichenfolgen im Textarray als oberste Schlüssel oder Arrayelemente existieren. Im Allgemeinen können Sie also überprüfen, ob ein JSON-Attribut, das ein Array enthält, alle Elemente enthält, nach denen Sie suchen.?|
– Prüft, ob einer der Zeichenfolgen im Textarray als oberster Schlüssel oder Arrayelement existiert. Im Allgemeinen können Sie also überprüfen, ob ein JSON-Attribut, das ein Array enthält, mindestens eines der Elemente enthält, nach denen Sie suchen.
Erforderliche DDL-Änderungen
Der obige Operator kann in HQL aufgrund besonderer Zeichen nicht verwendet werden. Deshalb müssen wir sie zum Beispiel in einer benutzerdefinierten SQL-Funktion einhüllen. Die Posjsonhelper
-Bibliothek benötigt zwei benutzerdefinierte SQL-Funktionen, die diese Operatoren einhüllen. Für die Standardeinstellung haben diese Funktionen die folgende Implementierung.
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;
Weitere Informationen zum Anpassen oder Programmgesteuert Hinzufügen erforderlicher DDL finden Sie im Abschnitt „Anwenden von DDL-Änderungen.“
„?&“ Wrapper
Das folgende Codebeispiel veranschaulicht, wie eine Abfrage erstellt wird, die sich anzeigt, für die Aufzeichnungen, für die JSON-Eigenschaft, die ein Array enthält, alle Zeichenfolgenelemente enthält, die wir durchsuchen.
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();
}
Falls die Tags zwei Elemente enthalten würden, würde Hibernate die folgende SQL generieren:
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
„?|“ Wrapper
Das folgende Codebeispiel veranschaulicht, wie eine Abfrage erstellt wird, die sich anzeigt, für die Aufzeichnungen, für die JSON-Eigenschaft, die ein Array enthält, mindestens ein Zeichenfolgenelement enthält, das wir durchsuchen.
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();
}
Falls die Tags zwei Elemente enthalten würden, würde Hibernate die folgende SQL generieren:
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
Für weitere Beispiele zum Einsatz numerischer Operatoren sehen Sie sich bitte die Demo dao object und dao tests an.
Schlussfolgerung
In einigen Fällen können Postgres JSON-Typen und Funktionen gute Alternativen zu NoSQL-Datenbanken sein. Dies könnte uns davor bewahren, NoSQL-Lösungen in unseren Technologiestapel aufzunehmen, was auch eine höhere Komplexität und zusätzliche Kosten mit sich bringen könnte.
Source:
https://dzone.com/articles/postgres-json-functions-with-hibernate-5