Ich habe eine Tabelle mit einer Spalte vom Typ JSON in meiner PostgreSQL-Datenbank (9.2). Es fällt mir schwer, diese Spalte einem JPA2-Entitätsfeldtyp zuzuordnen.
Ich habe versucht, String zu verwenden, aber wenn ich die Entität speichere, erhalte ich eine Ausnahme, dass Zeichen, die variieren, nicht in JSON konvertiert werden können.
Was ist der richtige Wertetyp für eine JSON-Spalte?
@Entity
public class MyEntity {
private String jsonPayload; // this maps to a json column
public MyEntity() {
}
}
Eine einfache Problemumgehung wäre das Definieren einer Textspalte.
Antworten:
Siehe PgJDBC-Fehler Nr. 265 .
PostgreSQL ist in Bezug auf Datentypkonvertierungen übermäßig streng. Es wird nicht implizit
text
in textähnliche Werte wiexml
und umgewandeltjson
.Die genau richtige Methode zur Lösung dieses Problems besteht darin, einen benutzerdefinierten Zuordnungstyp für den Ruhezustand zu schreiben, der die JDBC-
setObject
Methode verwendet. Dies kann ein ziemlicher Aufwand sein, daher möchten Sie PostgreSQL möglicherweise weniger streng gestalten, indem Sie eine schwächere Besetzung erstellen.Wie von @markdsievers in den Kommentaren und in diesem Blogbeitrag angegeben , umgeht die ursprüngliche Lösung in dieser Antwort die JSON-Validierung. Es ist also nicht wirklich das, was du willst. Es ist sicherer zu schreiben:
CREATE OR REPLACE FUNCTION json_intext(text) RETURNS json AS $$ SELECT json_in($1::cstring); $$ LANGUAGE SQL IMMUTABLE; CREATE CAST (text AS json) WITH FUNCTION json_intext(text) AS IMPLICIT;
AS IMPLICIT
teilt PostgreSQL mit, dass es konvertieren kann, ohne explizit dazu aufgefordert zu werden, sodass solche Dinge funktionieren:regress=# CREATE TABLE jsontext(x json); CREATE TABLE regress=# PREPARE test(text) AS INSERT INTO jsontext(x) VALUES ($1); PREPARE regress=# EXECUTE test('{}') INSERT 0 1
Vielen Dank an @markdsievers für den Hinweis auf das Problem.
quelle
cstring
Konvertierung sowieso durchlaufen , können Sie sie nicht einfach verwendenCREATE CAST (text AS json) WITH INOUT
?Wenn Sie interessiert sind, finden Sie hier einige Codeausschnitte, mit denen Sie den benutzerdefinierten Benutzertyp für den Ruhezustand einrichten können. Erweitern Sie zuerst den PostgreSQL-Dialekt, um ihn über den json-Typ zu informieren, dank Craig Ringer für den JAVA_OBJECT-Zeiger:
import org.hibernate.dialect.PostgreSQL9Dialect; import java.sql.Types; /** * Wrap default PostgreSQL9Dialect with 'json' type. * * @author timfulmer */ public class JsonPostgreSQLDialect extends PostgreSQL9Dialect { public JsonPostgreSQLDialect() { super(); this.registerColumnType(Types.JAVA_OBJECT, "json"); } }
Als nächstes implementieren Sie org.hibernate.usertype.UserType. Die folgende Implementierung ordnet String-Werte dem json-Datenbanktyp zu und umgekehrt. Denken Sie daran, dass Strings in Java unveränderlich sind. Eine komplexere Implementierung könnte verwendet werden, um benutzerdefinierte Java-Beans auch JSON zuzuordnen, das in der Datenbank gespeichert ist.
package foo; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.usertype.UserType; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; /** * @author timfulmer */ public class StringJsonUserType implements UserType { /** * Return the SQL type codes for the columns mapped by this type. The * codes are defined on <tt>java.sql.Types</tt>. * * @return int[] the typecodes * @see java.sql.Types */ @Override public int[] sqlTypes() { return new int[] { Types.JAVA_OBJECT}; } /** * The class returned by <tt>nullSafeGet()</tt>. * * @return Class */ @Override public Class returnedClass() { return String.class; } /** * Compare two instances of the class mapped by this type for persistence "equality". * Equality of the persistent state. * * @param x * @param y * @return boolean */ @Override public boolean equals(Object x, Object y) throws HibernateException { if( x== null){ return y== null; } return x.equals( y); } /** * Get a hashcode for the instance, consistent with persistence "equality" */ @Override public int hashCode(Object x) throws HibernateException { return x.hashCode(); } /** * Retrieve an instance of the mapped class from a JDBC resultset. Implementors * should handle possibility of null values. * * @param rs a JDBC result set * @param names the column names * @param session * @param owner the containing entity @return Object * @throws org.hibernate.HibernateException * * @throws java.sql.SQLException */ @Override public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { if(rs.getString(names[0]) == null){ return null; } return rs.getString(names[0]); } /** * Write an instance of the mapped class to a prepared statement. Implementors * should handle possibility of null values. A multi-column type should be written * to parameters starting from <tt>index</tt>. * * @param st a JDBC prepared statement * @param value the object to write * @param index statement parameter index * @param session * @throws org.hibernate.HibernateException * * @throws java.sql.SQLException */ @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.OTHER); return; } st.setObject(index, value, Types.OTHER); } /** * Return a deep copy of the persistent state, stopping at entities and at * collections. It is not necessary to copy immutable objects, or null * values, in which case it is safe to simply return the argument. * * @param value the object to be cloned, which may be null * @return Object a copy */ @Override public Object deepCopy(Object value) throws HibernateException { return value; } /** * Are objects of this type mutable? * * @return boolean */ @Override public boolean isMutable() { return true; } /** * Transform the object into its cacheable representation. At the very least this * method should perform a deep copy if the type is mutable. That may not be enough * for some implementations, however; for example, associations must be cached as * identifier values. (optional operation) * * @param value the object to be cached * @return a cachable representation of the object * @throws org.hibernate.HibernateException * */ @Override public Serializable disassemble(Object value) throws HibernateException { return (String)this.deepCopy( value); } /** * Reconstruct an object from the cacheable representation. At the very least this * method should perform a deep copy if the type is mutable. (optional operation) * * @param cached the object to be cached * @param owner the owner of the cached object * @return a reconstructed object from the cachable representation * @throws org.hibernate.HibernateException * */ @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return this.deepCopy( cached); } /** * During merge, replace the existing (target) value in the entity we are merging to * with a new (original) value from the detached entity we are merging. For immutable * objects, or null values, it is safe to simply return the first parameter. For * mutable objects, it is safe to return a copy of the first parameter. For objects * with component values, it might make sense to recursively replace component values. * * @param original the value from the detached entity being merged * @param target the value in the managed entity * @return the value to be merged */ @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } }
Jetzt müssen nur noch die Entitäten mit Anmerkungen versehen werden. Fügen Sie so etwas in die Klassendeklaration der Entität ein:
@TypeDefs( {@TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)})
Kommentieren Sie dann die Eigenschaft:
@Type(type = "StringJsonObject") public String getBar() { return bar; }
Hibernate kümmert sich darum, die Spalte mit dem Typ json für Sie zu erstellen, und übernimmt die Zuordnung hin und her. Fügen Sie der Benutzertypimplementierung zusätzliche Bibliotheken für eine erweiterte Zuordnung hinzu.
Hier ist ein kurzes Beispiel für ein GitHub-Projekt, wenn jemand damit herumspielen möchte:
https://github.com/timfulmer/hibernate-postgres-jsontype
quelle
Maven-Abhängigkeit
Das erste , was Sie tun müssen, ist die folgende einzurichten Hibernate Typen Maven Abhängigkeit in Ihrer Projekt -
pom.xml
Konfigurationsdatei:<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>${hibernate-types.version}</version> </dependency>
Domänenmodell
Wenn Sie jetzt PostgreSQL verwenden, müssen Sie das
JsonBinaryType
entweder auf Klassenebene oder in einem Deskriptor auf Paketebene von package-info.java deklarieren , wie folgt :@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
Die Entitätszuordnung sieht folgendermaßen aus:
@Type(type = "jsonb") @Column(columnDefinition = "json") private Location location;
Wenn Sie Hibernate 5 oder höher verwenden, wird der
JSON
Typ automatisch von der registriertPostgre92Dialect
.Andernfalls müssen Sie es selbst registrieren:
public class PostgreSQLDialect extends PostgreSQL91Dialect { public PostgreSQL92Dialect() { super(); this.registerColumnType( Types.JAVA_OBJECT, "json" ); } }
In MySQL können Sie in diesem Artikel nachlesen, wie Sie JSON-Objekte mithilfe von MySQL zuordnen können
JsonStringType
.quelle
Bei Interesse können Sie JPA 2.1
@Convert
/@Converter
Funktionalität mit Hibernate verwenden. Sie müssten jedoch den JDBC-Treiber pgjdbc-ng verwenden . Auf diese Weise müssen Sie keine proprietären Erweiterungen, Dialekte und benutzerdefinierten Typen pro Feld verwenden.@javax.persistence.Converter public static class MyCustomConverter implements AttributeConverter<MuCustomClass, String> { @Override @NotNull public String convertToDatabaseColumn(@NotNull MuCustomClass myCustomObject) { ... } @Override @NotNull public MuCustomClass convertToEntityAttribute(@NotNull String databaseDataAsJSONString) { ... } } ... @Convert(converter = MyCustomConverter.class) private MyCustomClass attribute;
quelle
Ich hatte ein ähnliches Problem mit Postgres (javax.persistence.PersistenceException: org.hibernate.MappingException: Keine Dialektzuordnung für JDBC-Typ: 1111) beim Ausführen nativer Abfragen (über EntityManager), bei denen JSON-Felder in der Projektion abgerufen wurden, obwohl die Entity-Klasse vorhanden war mit TypeDefs kommentiert. Die gleiche in HQL übersetzte Abfrage wurde problemlos ausgeführt. Um dies zu lösen, musste ich JsonPostgreSQLDialect folgendermaßen ändern:
public class JsonPostgreSQLDialect extends PostgreSQL9Dialect { public JsonPostgreSQLDialect() { super(); this.registerColumnType(Types.JAVA_OBJECT, "json"); this.registerHibernateType(Types.OTHER, "myCustomType.StringJsonUserType"); }
Wobei myCustomType.StringJsonUserType der Klassenname der Klasse ist, die den json-Typ implementiert (von oben, Antwort von Tim Fulmer).
quelle
Ich habe viele Methoden ausprobiert, die ich im Internet gefunden habe. Die meisten funktionieren nicht, einige sind zu komplex. Das Folgende funktioniert für mich und ist viel einfacher, wenn Sie nicht die strengen Anforderungen für die PostgreSQL-Typvalidierung haben.
Machen Sie den PostgreSQL-JDBC-Zeichenfolgentyp wie nicht angegeben, wie z
<connection-url> jdbc:postgresql://localhost:test?stringtype=unspecified </connection-url>
quelle
Es ist einfacher, dies zu tun, ohne eine Funktion mithilfe von zu erstellen
WITH INOUT
CREATE TABLE jsontext(x json); INSERT INTO jsontext VALUES ($${"a":1}$$::text); ERROR: column "x" is of type json but expression is of type text LINE 1: INSERT INTO jsontext VALUES ($${"a":1}$$::text); CREATE CAST (text AS json) WITH INOUT AS ASSIGNMENT; INSERT INTO jsontext VALUES ($${"a":1}$$::text); INSERT 0 1
quelle
Ich bin darauf gestoßen und wollte keine Inhalte über eine Verbindungszeichenfolge aktivieren und implizite Konvertierungen zulassen. Zuerst habe ich versucht, @Type zu verwenden, aber da ich einen benutzerdefinierten Konverter zum Serialisieren / Deserialisieren einer Map zu / von JSON verwende, konnte ich keine @ Type-Annotation anwenden. Es stellte sich heraus, dass ich in meiner @ Column-Annotation nur columnDefinition = "json" angeben musste.
@Convert(converter = HashMapConverter.class) @Column(name = "extra_fields", columnDefinition = "json") private Map<String, String> extraFields;
quelle