88

PostgreSQL DB (9.2) に JSON 型の列を持つテーブルがあります。この列を JPA2 Entity フィールド型にマップするのに苦労しています。

String を使用しようとしましたが、エンティティを保存すると、JSON に変化する文字を変換できないという例外が発生します。

JSON 列を処理するときに使用する正しい値の型は何ですか?

@Entity
public class MyEntity {

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

    public MyEntity() {
    }
}

簡単な回避策は、テキスト列を定義することです。

4

10 に答える 10

81

興味がある場合は、Hibernate カスタム ユーザー タイプを配置するためのコード スニペットをいくつか示します。JAVA_OBJECT ポインターの Craig Ringer に感謝します。

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

次に org.hibernate.usertype.UserType を実装します。以下の実装では、String 値を json データベース タイプに、またはその逆にマッピングします。文字列は Java では不変であることを忘れないでください。より複雑な実装を使用して、カスタム Java Bean をデータベースに格納されている JSON にマップすることもできます。

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

あとは、エンティティに注釈を付けるだけです。エンティティのクラス宣言に次のように記述します。

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

次に、プロパティに注釈を付けます。

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

Hibernate は、json タイプの列を作成し、マッピングを前後に処理します。より高度なマッピングのために、追加のライブラリをユーザー タイプの実装に挿入します。

試してみたい人のために、GitHub プロジェクトの簡単なサンプルを次に示します。

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

于 2013-04-16T23:45:45.070 に答える
38

PgJDBC バグ #265 を参照してください

PostgreSQL は、データ型の変換に関して過度に、うっとうしいほど厳密です。やtextなどのテキストのような値にも暗黙的にキャストしません。xmljson

この問題を解決する厳密に正しい方法は、JDBCsetObjectメソッドを使用するカスタム Hibernate マッピング タイプを作成することです。これはかなり面倒なので、より弱いキャストを作成して PostgreSQL の厳密性を下げたいだけかもしれません。

コメントとこのブログ投稿で@markdsievers が指摘したように、この回答の元のソリューションは JSON 検証をバイパスします。だから、それは本当にあなたが望むものではありません。次のように書く方が安全です:

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明示的に指示されなくても変換できることをPostgreSQLに伝え、次のようなことができるようにします:

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

問題を指摘してくれた @markdsievers に感謝します。

于 2013-04-13T02:03:32.927 に答える
13

誰かが興味を持っている場合は、Hibernate でJPA 2.1 @Convert/機能を使用できます。ただし、 pgjdbc-ng JDBC ドライバー@Converterを使用する必要があります。この方法では、フィールドごとに独自の拡張機能、方言、およびカスタム タイプを使用する必要はありません。

@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;
于 2015-05-04T05:27:53.157 に答える
8

インターネットで見つけた多くの方法を試しましたが、ほとんどが機能せず、複雑すぎるものもあります。以下のものは私にとってはうまくいき、PostgreSQLの型検証の厳格な要件がない場合ははるかに簡単です.

次のように、PostgreSQL jdbc 文字列型を未指定にします。 <connection-url> jdbc:postgresql://localhost:test?stringtype=‌​unspecified </connect‌​ion-url>

于 2016-12-07T17:12:10.423 に答える
3

Entityクラスが使用されているにもかかわらず、プロジェクションでjsonフィールドを取得するネイティブクエリを(EntityManager経由で)実行すると、Postgres(javax.persistence.PersistenceException:org.hibernate.MappingException:JDBCタイプのダイアレクトマッピングなし:1111)で同様の問題が発生しましたTypeDefs で注釈が付けられています。HQL に変換された同じクエリは問題なく実行されました。これを解決するには、JsonPostgreSQLDialect を次のように変更する必要がありました。

public class JsonPostgreSQLDialect extends PostgreSQL9Dialect {

public JsonPostgreSQLDialect() {

    super();

    this.registerColumnType(Types.JAVA_OBJECT, "json");
    this.registerHibernateType(Types.OTHER, "myCustomType.StringJsonUserType");
}

myCustomType.StringJsonUserType は、json 型を実装するクラスのクラス名です (上から、Tim Fulmer の回答)。

于 2015-09-30T13:09:11.810 に答える