Tengo una tabla con una columna de tipo JSON en mi PostgreSQL DB (9.2). Tengo un tiempo difícil para asignar esta columna a un JPA2 Entidad tipo de campo.

He intentado utilizar la Cadena, pero cuando me guarda la entidad obtengo una excepción que no puede convertir variables de caracteres a JSON.

¿Cuál es el tipo de valor correcto a utilizar cuando se trata de un JSON columna?

@Entity
public class MyEntity {

    private String jsonPayload; //this maps to a json column

    public MyEntity() {
    }
}

Una solución sencilla sería definir una columna de texto.

InformationsquelleAutor | 2013-04-12

7 Comentarios

  1. 36

    Ver PgJDBC bug #265.

    PostgreSQL es demasiado, demasiado estrictas acerca de las conversiones de tipos de datos. No implícitamente text incluso a texto-como valores tales como la xml y json.

    La estrictamente manera correcta de resolver este problema es escribir una costumbre de mapeo Hibernate tipo que utiliza JDBC setObject método. Esto puede ser un poco justo de molestia, por lo que es posible que sólo quieren hacer de PostgreSQL menos estricta mediante la creación de un débil elenco.

    Como se nota por @markdsievers en los comentarios y este blog, la solución original en esta respuesta elude JSON de validación. Así que no es realmente lo que quieres. Es más seguro para escribir:

    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 dice PostgreSQL se puede convertir sin ser explícitamente dicho, permitiendo cosas como esta para trabajar:

    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

    Gracias a @markdsievers para señalar el problema.

    • gracias por la solución!
    • Vale la pena leer la resultante blog de esta respuesta. Enparticular la sección de comentarios que pone de relieve los peligros de esta (permite no válido json) y el / alternativas de solución superior.
    • Gracias. He actualizado el post con una corrección de la solución.
    • No hay problema. Gracias por su prolífica PG / JPA / JDBC contribuciones, muchos han sido de gran ayuda para mí.
    • Puesto que usted va a través de la cstring la conversión de todos modos, no podía simplemente utilizar CREATE CAST (text AS json) WITH INOUT?
    • esa solución también funcionó a la perfección para mí (y de lo que había visto, se produce un error no válido JSON, como debe ser). Gracias!
    • Yo le robó su idea, gracias: dba.stackexchange.com/a/163120/6219
    • debe de haber contestado. Eso habría sido una valiosa contribución — aquí es sin embargo stackoverflow.com/a/42032206/124486
    • El uso de INOUT es simple. stackoverflow.com/a/42032206/124486

  2. 74

    Si usted está interesado, aquí hay un par de fragmentos de código para obtener la Hibernación de usuario personalizada tipo en el lugar. Primero extender el PostgreSQL dialecto de decir sobre el json tipo, gracias a Craig de Timbre para el JAVA_OBJECT puntero:

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

    Siguiente implementar org.hibernate.usertype.UserType. La aplicación a continuación los mapas de Cadena de valores para el json que tipo de base de datos, y viceversa. Recuerde que las Cadenas son inmutables en Java. Una implementación más compleja podría ser utilizado para mapa personalizado Java beans para JSON almacenados en la base de datos.

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

    Ahora todo lo que queda es la anotación de las entidades. Algo como esto en la entidad de la declaración de la clase:

    @TypeDefs( {@TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)})

    A continuación, anotar la propiedad:

    @Type(type = "StringJsonObject")
    public String getBar() {
    return bar;
    }

    Hibernate se encargará de la creación de la columna con json tipo para usted, y manejar la asignación de ida y vuelta. Inyectar de bibliotecas adicionales en el tipo de usuario la aplicación para obtener más avanzadas de mapeo.

    Aquí está una muestra rápida de GitHub del proyecto si alguien quiere jugar con ella:

    https://github.com/timfulmer/hibernate-postgres-jsontype

    • gracias por la exhaustiva de ejemplos de código! Estoy usando un campo de texto normal, pero yo podría tomar su enfoque en el futuro
    • Gracias por tomarse el tiempo para escribir esto. En intensamente me resulta frustrante que JPA no define SPI ganchos para tipos definidos por el usuario para ser escrito en un JPA-proveedor de manera independiente.
    • No se preocupe chicos, que terminó con el código y esta página en frente de mí y pensé ¿por qué no 🙂 Que podría ser el inconveniente de que el proceso Java. Tenemos algunos bastante bien pensado a través de soluciones a problemas difíciles, pero no es fácil entrar y añadir una buena idea como genérico SPI para que los nuevos tipos. Nos quedamos con lo que los implementadores, Hibernate en este caso, en el lugar.
    • hay un problema en el código de la implementación para nullSafeGet. En lugar de if(rs.wasNull()) usted debe hacer si(rs.getString(nombres[0]) == null). No estoy seguro de lo que rs.wasNull (), pero en mi caso se me quemó devolviendo true, cuando el valor que yo estaba buscando era, de hecho, no es null.
    • Buena captura! Siento que hayas tenido que pasar por eso. He actualizado el código de arriba.
    • Esta solución funcionó muy bien con Hibernate 4.2.7 excepto cuando la recuperación nulo de json columnas con el error «No Dialecto asignación para JDBC de tipo: 1111′. Sin embargo, agregar la siguiente línea en el dialecto de la clase arreglado: este.registerHibernateType(Tipos.OTROS, «StringJsonUserType»);
    • He actualizado el ejemplo de arriba para funcionar con la última de Hibernación. Buena captura.
    • JPA 2.1 tiene un convertidor de anotación, pero sólo los mapas de tipos estándar (como cadena de caracteres) a su tipos personalizados (por ejemplo, un java bean).
    • no se olvide de cambiar su estado de hibernación.propiedades de la sustitución de la propiedad «dialecto» contenido con ‘JsonPostgreSQLDialect’
    • He intentado hacer esto y trabaja para la lectura/la persistencia de la Entidad. Sin embargo, si me necesitan para ejecutar una consulta en contra de las Restricciones.sqlRestriction(sql, Objeto, Tipo) en el Jsonb columna, el método ha de tomar a un Tipo en lugar de UserType. Parece ser que la única solución es aplicar el Tipo de cambio?
    • Yo también estoy mirando para ejecutar una consulta. pero show ‘no Reconocido campo de’ error interno campos de json.¿Me pueden ayudar.
    • No veo ningún código en el github-proyecto 😉 por CIERTO: ¿no sería útil disponer de este código como una biblioteca para su reutilización?
    • ¿Cómo funciona esta solución son diferentes para Hibernar 5?
    • Bonita respuesta tyvm Tim !
    • Puedes hacer esto con returnedClass como un JSONObject o un Objeto?

  3. 13

    Como he explicado en este artículo, es muy fácil persistir un objeto JSON usando Hibernate.

    Usted no tiene que crear todos estos tipos de forma manual, usted puede conseguir simplemente
    ellos a través de Maven Central utilizando la siguiente dependencia:

    <dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version> 
    </dependency> 

    Para obtener más información, consulte la hibernate-tipos de proyecto de código abierto.

    Ahora, para explicar cómo funciona todo.

    Escribí un artículo acerca de cómo puede asignar objetos JSON en ambos PostgreSQL y MySQL.

    Para PostgreSQL, usted necesita para enviar el objeto JSON en una forma binaria:

    public class JsonBinaryType
    extends AbstractSingleColumnStandardBasicType<Object> 
    implements DynamicParameterizedType {
    public JsonBinaryType() {
    super( 
    JsonBinarySqlTypeDescriptor.INSTANCE, 
    new JsonTypeDescriptor()
    );
    }
    public String getName() {
    return "jsonb";
    }
    @Override
    public void setParameterValues(Properties parameters) {
    ((JsonTypeDescriptor) getJavaTypeDescriptor())
    .setParameterValues(parameters);
    }
    }

    La JsonBinarySqlTypeDescriptor se parece a esto:

    public class JsonBinarySqlTypeDescriptor
    extends AbstractJsonSqlTypeDescriptor {
    public static final JsonBinarySqlTypeDescriptor INSTANCE = 
    new JsonBinarySqlTypeDescriptor();
    @Override
    public <X> ValueBinder<X> getBinder(
    final JavaTypeDescriptor<X> javaTypeDescriptor) {
    return new BasicBinder<X>(javaTypeDescriptor, this) {
    @Override
    protected void doBind(
    PreparedStatement st, 
    X value, 
    int index, 
    WrapperOptions options) throws SQLException {
    st.setObject(index, 
    javaTypeDescriptor.unwrap(
    value, JsonNode.class, options), getSqlType()
    );
    }
    @Override
    protected void doBind(
    CallableStatement st, 
    X value, 
    String name, 
    WrapperOptions options)
    throws SQLException {
    st.setObject(name, 
    javaTypeDescriptor.unwrap(
    value, JsonNode.class, options), getSqlType()
    );
    }
    };
    }
    }

    y la JsonTypeDescriptor como este:

    public class JsonTypeDescriptor
    extends AbstractTypeDescriptor<Object> 
    implements DynamicParameterizedType {
    private Class<?> jsonObjectClass;
    @Override
    public void setParameterValues(Properties parameters) {
    jsonObjectClass = ( (ParameterType) parameters.get( PARAMETER_TYPE ) )
    .getReturnedClass();
    }
    public JsonTypeDescriptor() {
    super( Object.class, new MutableMutabilityPlan<Object>() {
    @Override
    protected Object deepCopyNotNull(Object value) {
    return JacksonUtil.clone(value);
    }
    });
    }
    @Override
    public boolean areEqual(Object one, Object another) {
    if ( one == another ) {
    return true;
    }
    if ( one == null || another == null ) {
    return false;
    }
    return JacksonUtil.toJsonNode(JacksonUtil.toString(one)).equals(
    JacksonUtil.toJsonNode(JacksonUtil.toString(another)));
    }
    @Override
    public String toString(Object value) {
    return JacksonUtil.toString(value);
    }
    @Override
    public Object fromString(String string) {
    return JacksonUtil.fromString(string, jsonObjectClass);
    }
    @SuppressWarnings({ "unchecked" })
    @Override
    public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
    if ( value == null ) {
    return null;
    }
    if ( String.class.isAssignableFrom( type ) ) {
    return (X) toString(value);
    }
    if ( Object.class.isAssignableFrom( type ) ) {
    return (X) JacksonUtil.toJsonNode(toString(value));
    }
    throw unknownUnwrap( type );
    }
    @Override
    public <X> Object wrap(X value, WrapperOptions options) {
    if ( value == null ) {
    return null;
    }
    return fromString(value.toString());
    }
    }

    Ahora, usted tiene que declarar el nuevo tipo en cualquiera de las clases o en una package-info.java de nivel de paquete descriptior:

    @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)

    Y la entidad de la asignación tendrá un aspecto como este:

    @Type(type = "jsonb")
    @Column(columnDefinition = "json")
    private Location location;

    Si usted está usando Hibernate 5 o más, el JSON tipo de registrado automáticamente por Postgre92Dialect.

    De lo contrario, usted necesita registrarse a sí mismo:

    public class PostgreSQLDialect extends PostgreSQL91Dialect {
    public PostgreSQL92Dialect() {
    super();
    this.registerColumnType( Types.JAVA_OBJECT, "json" );
    }
    }
    • Buen ejemplo, pero puede ser usado con algunos genérico DAO, como Spring Data JPA los repositorios de datos de consulta sin las consultas nativas como podemos hacer con MongoDB? Yo no encuentro ninguna respuesta válida o solución a este caso. Sí, podemos almacenar los datos, y podemos recuperar de ellos mediante el filtrado de las columnas en RDBMS, pero no puedo filtrar por JSONB coluns hasta ahora. Me gustaría que me equivoco y no es la solución.
    • Sí, se puede. Pero necesitas utilizar nativ consultas, las cuales son compatibles con Spring Data JPA demasiado.
    • Veo, que en realidad era mi questin, si podemos ir sin las consultas nativas, pero sólo a través de los objetos de los métodos. Algo como @anotación de Documentos para MongoDB estilo. Así que supongo que esto no está tan lejos, en el caso de PostgreSQL y la única solución es nativo de consultas -> desagradable :-), pero gracias por la confirmación.
    • Sería bueno ver que en el futuro, algo como entidad que representa la tabla y anotación de documentos en los campos de tipo de json y puedo usar la Primavera de repositorios para hacer CRUD cosas sobre la marcha. Creo que es que estoy generando bastante avanzada API REST para bases de datos con la Primavera. Pero con JSON en lugar estoy mirando bastante inesperado sobrecarga, así que voy a necesitar para el proceso de cada documento con generar consultas.
    • Usted puede usar el modo de Hibernación OGM con MongoDB si JSON es su único almacén.
    • Menciono MongoDB sólo como ejemplo. Mi pregunta es más acerca de: Qué sabe usted acerca de algunas estándar JPA que podría traer mismas funcionalidades con respecto a la CONSULTA de datos en PostgreSQL JSONB estructuras de la misma manera como se puede hacer con JPA (hibernate) con MongoDB?
    • No hay tal estándar JPA 2.2. Ahora que Java EE es de código abierto, usted podría involucrarse en la defensa de la misma. Personalmente, yo no creo que sea una prioridad para JPA, porque consultas SQL nativas son una Varita Mágica.
    • Hay JUnit apoyo para JSONB en springboot proyectos?
    • Esta solución funcionó para mí sólo después de cambiar la clase Java del campo de json a Jackson JsonNode. No trabajo para un pojo o un raw cadena json.
    • El repositorio de GitHub asociados con la hibernate-types contiene la unidad de pruebas que muestran cómo asignar a un Pojo así.

  4. 11

    En caso de que alguien esté interesado, puede utilizar JPA 2.1 @Convert /@Converter funcionalidad con Hibernate. Usted tendría que utilizar el pgjdbc-ng controlador JDBC aunque. De esta manera usted no tiene que utilizar cualquier extensiones propietarias, dialectos y tipos personalizados por campo.

    @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;
    • Esto suena útil – ¿qué tipo debe convertir entre ser capaz de escribir JSON? Es <MyCustomClass, Cadena> o de algún otro tipo?
    • Gracias – apenas comprobado que funciona para mí (JPA 2.1, Hibernate 4.3.10, pgjdbc-ng 0.5, Postgres 9.3)
    • Es posible hacer que funcione sin especificando @Column(columnDefinition = «json») en el campo? Hibernate es hacer un tipo de datos varchar(255) sin esta definición.
    • Hibernate no podemos saber qué tipo de columna que desea no, pero insisto en que es de Hibernación de la responsabilidad de actualizar el esquema de base de datos. Así que supongo que recoge el defecto.
  5. 3

    He tenido un problema similar con Postgres (javax.la persistencia.PersistenceException: org.hibernate.MappingException: No Dialecto asignación para JDBC de tipo: 1111) cuando la ejecución de las consultas nativas (a través de EntityManager) que recuperan json campos en la proyección, aunque la clase de Entidad se ha anotado con las definiciones de tipos.
    La misma consulta traducido en HQL fue ejecutado sin ningún problema.
    Para solucionar esto, he tenido que modificar JsonPostgreSQLDialect de esta manera:

    public class JsonPostgreSQLDialect extends PostgreSQL9Dialect {
    public JsonPostgreSQLDialect() {
    super();
    this.registerColumnType(Types.JAVA_OBJECT, "json");
    this.registerHibernateType(Types.OTHER, "myCustomType.StringJsonUserType");
    }

    Donde myCustomType.StringJsonUserType es el nombre de clase de la clase que implementa el json tipo (desde arriba, Tim Fulmer respuesta) .

  6. 2

    He intentado muchos métodos que he encontrado en Internet, la mayoría de ellas no están trabajando, algunos de ellos son demasiado complejos. El de abajo uno que funciona para mí y es mucho más simple si usted no tiene que requisitos estrictos para PostgreSQL tipo de validación.

    Hacer jdbc de PostgreSQL cadena de tipo no especificado, como

    <connection-url>
    jdbc:postgresql://localhost:test?stringtype=‌​unspecified
    </connect‌​ion-url>

  7. 1

    Hay un más fácil hacer esto que no implican la creación de una función mediante el uso de CON 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
    • Gracias, lo utilizó para fundición de varchar a ltree, funciona a la perfección.

Dejar respuesta

Please enter your comment!
Please enter your name here