PostgreSQL JSON कॉलम को हाइबरनेट इकाई गुण से मैप करना


81

मेरे पास मेरी PostgreSQL DB (9.2) में JSON प्रकार के कॉलम के साथ एक तालिका है। मेरे पास इस कॉलम को JPA2 इकाई क्षेत्र प्रकार पर मैप करने के लिए एक कठिन समय है।

मैंने स्ट्रिंग का उपयोग करने की कोशिश की, लेकिन जब मैं इकाई को बचाता हूं तो मुझे एक अपवाद मिलता है कि यह चरित्र को JSON में परिवर्तित नहीं कर सकता है।

JSON कॉलम से निपटने के लिए उपयोग करने के लिए सही मूल्य प्रकार क्या है?

@Entity
public class MyEntity {

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

    public MyEntity() {
    }
}

टेक्स्ट कॉलम को परिभाषित करने के लिए एक सरल समाधान होगा।


2
मुझे पता है कि यह थोड़ा पुराना है, लेकिन इसी तरह के प्रश्न के लिए मेरे उत्तर stackoverflow.com/a/26126168/1535995 पर एक नज़र डालें
Sasa7812

vladmihalcea.com/ ... यह ट्यूटोरियल बहुत सीधा है
SG33

जवाबों:


37

PgJDBC बग # 265 देखें

PostgreSQL डेटा प्रकार रूपांतरणों के बारे में अत्यधिक, गुस्से में सख्त है। यह परोक्ष डाली नहीं होगा textयहां तक कि पाठ की तरह इस तरह के रूप मूल्यों के लिए xmlऔर json

इस समस्या को हल करने के लिए सख्ती से सही तरीका एक कस्टम हाइबरनेट मैपिंग प्रकार लिखना है जो JDBC setObjectविधि का उपयोग करता है । यह काफी हद तक परेशानी का सबब बन सकता है, इसलिए हो सकता है कि आप कमज़ोर कास्ट बनाकर 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 के लिए धन्यवाद।


2
इस उत्तर के परिणामी ब्लॉग पोस्ट को पढ़ने के लायक । टिप्पणी अनुभाग में इस के खतरों पर प्रकाश डाला गया (अमान्य जोंसन की अनुमति देता है) और वैकल्पिक / बेहतर समाधान।
मार्कशीट

@ मार्कशीट थैंक्यू। मैंने पोस्ट को एक सही समाधान के साथ अपडेट किया है।
क्रेग रिंगर

@ क्रेगिंगर कोई समस्या नहीं है। आपके शानदार पीजी / जेपीए / जेडीबीसी योगदान के लिए धन्यवाद, बहुत से मेरे लिए बहुत मददगार रहे हैं।
मार्कशीट

1
@CraigRinger चूंकि आप cstringवैसे भी रूपांतरण के माध्यम से जा रहे हैं , क्या आप बस उपयोग नहीं कर सकते CREATE CAST (text AS json) WITH INOUT?
निक बार्न्स

@NickBarnes ने उस समाधान को भी पूरी तरह से मेरे लिए काम किया (और जो मैंने देखा था, वह अमान्य JSON पर विफल है, जैसा कि यह होना चाहिए)। धन्यवाद!
जीरो डिविजनल

76

यदि आप रुचि रखते हैं, तो यहां हाइबरनेट कस्टम उपयोगकर्ता प्रकार प्राप्त करने के लिए कुछ कोड स्निपेट हैं। JSONA_OBJECT पॉइंटर के लिए क्रेग रिंगर को धन्यवाद देने के लिए सबसे पहले PostgreSQL बोली का विस्तार करके इसे json प्रकार के बारे में बताएं।

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 लागू करें। नीचे दिए गए कार्यान्वयन, नक्शे को स्ट्रिंग मानों को json डेटाबेस प्रकार, और इसके विपरीत। याद रखें स्ट्रिंग्स जावा में अपरिवर्तनीय हैं। एक अधिक जटिल कार्यान्वयन का उपयोग कस्टम जावा बीन्स को 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;
}

हाइबरनेट आपके लिए json प्रकार के साथ कॉलम बनाने का ख्याल रखेगा, और मैपिंग को आगे-पीछे करेगा। अधिक उन्नत मैपिंग के लिए उपयोगकर्ता प्रकार कार्यान्वयन में अतिरिक्त पुस्तकालयों को इंजेक्ट करें।

यहाँ एक त्वरित नमूना GitHub परियोजना है अगर कोई इसके साथ खेलना चाहता है:

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


2
कोई चिंता नहीं दोस्तों, मैं कोड और मेरे सामने इस पृष्ठ के साथ समाप्त हो गया और सोचा कि क्यों नहीं :) यह जावा प्रक्रिया का नकारात्मक पहलू हो सकता है। हम कठिन समस्याओं के समाधान के माध्यम से कुछ बहुत अच्छी तरह से सोचते हैं, लेकिन नए प्रकारों के लिए जेनेरिक एसपीआई जैसे अच्छे विचार को जोड़ना और जोड़ना आसान नहीं है। हम इस मामले में हाइबरनेट को लागू करने वाले जो कुछ भी हैं, उसके साथ छोड़ दिए गए हैं।
टिम फुलमर

3
nullSafeGet के लिए आपके कार्यान्वयन कोड में कोई समस्या है। इसके बजाय अगर (rs.wasNull ()) आपको करना चाहिए तो (rs.getString (नाम [0]) == null)। मुझे यकीन नहीं है कि rswasNull () क्या करता है, लेकिन मेरे मामले में इसने मुझे सच में लौटा दिया, जब मैं जिस मूल्य की तलाश कर रहा था वह वास्तव में अशक्त नहीं था।
rtcarlson

1
@rtcarlson अच्छा कैच! खेद है कि आपको इससे गुजरना पड़ा। मैंने ऊपर कोड अपडेट किया है।
टिम फुल्मर

3
यह समाधान हाइबरनेट 4.2.7 के साथ अच्छी तरह से काम करता है, सिवाय इसके कि जब 'कॉलम के साथ जदबीसी प्रकार: 1111' के लिए कोई कॉलम मैपिंग त्रुटि वाले अशक्त कॉलम को पुनर्प्राप्त नहीं करता। हालाँकि, निम्न वर्ग को बोली वर्ग में जोड़ने से यह निश्चित हो गया: this.registerHibernateType (Types.OTHER, "StringJsonUserType");
ओलिवरगुएन्थेर

7
मुझे लिंक किए गए गथब-प्रोजेक्ट पर कोई कोड दिखाई नहीं देता; ;-) बीटीडब्लू: क्या पुन: उपयोग के लिए पुस्तकालय के रूप में इस कोड का होना उपयोगी नहीं होगा?
rü-

21

मावेन निर्भरता

पहली चीज जो आपको करने की ज़रूरत है, वह है अपनी प्रोजेक्ट कॉन्फ़िगरेशन फ़ाइल में निम्न हाइबरनेट प्रकार मावेन निर्भरता सेट करना pom.xml:

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

डोमेन मॉडल

अब, यदि आप PostgreSQL का उपयोग कर रहे हैं, तो आपको इस तरह की JsonBinaryTypeकक्षा स्तर पर या पैकेज-info.java पैकेज-स्तरीय विवरणक में घोषित करने की आवश्यकता है :

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

और, इकाई मानचित्रण इस तरह दिखेगा:

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

यदि आप 5 या बाद के हाइबरनेट का उपयोग कर रहे हैं, तो JSONप्रकार स्वचालित रूप से पंजीकृत है Postgre92Dialect

अन्यथा, आपको इसे स्वयं पंजीकृत करने की आवश्यकता है:

public class PostgreSQLDialect extends PostgreSQL91Dialect {

    public PostgreSQL92Dialect() {
        super();
        this.registerColumnType( Types.JAVA_OBJECT, "json" );
    }
}

MySQL के लिए, आप JSON ऑब्जेक्ट को मैप कर सकते हैं JsonStringType


अच्छा उदाहरण है, लेकिन यह कुछ सामान्य डीएओ के साथ इस्तेमाल किया जा सकता है, जैसे कि स्प्रिंग डेटा जेपीए रिपॉजिटरी जैसे डेटा को क्वेरी करने के लिए देशी प्रश्नों के बिना जैसे हम MongoDB के साथ कर सकते हैं? मुझे इस मामले का कोई मान्य उत्तर या समाधान नहीं मिला। हां हम डेटा संग्रहीत कर सकते हैं, और हम उन्हें RDBMS में कॉलम फ़िल्टर करके पुन: प्राप्त कर सकते हैं, लेकिन मैं JSONB कॉलन द्वारा अब तक फ़िल्टर नहीं कर सकता। काश मैं गलत हूं और इस तरह का समाधान है।
केंसई

हाँ तुम कर सकते हो। लेकिन आपको नेटिव प्रश्नों का उपयोग करने की आवश्यकता है जो स्प्रिंग डेटा जेपीए द्वारा समर्थित हैं।
व्लाद मिहालसी

मैं देखता हूं, कि वास्तव में मेरा खोज था, अगर हम देशी प्रश्नों के बिना जा सकते हैं, लेकिन सिर्फ वस्तुओं के तरीकों के माध्यम से। MongoDB शैली के लिए @Document एनोटेशन जैसा कुछ। इसलिए मुझे लगता है कि यह अब तक PostgreSQL के मामले में नहीं है और एकमात्र समाधान मूल प्रश्न है -> गंदा :-), लेकिन पुष्टि के लिए धन्यवाद।
केंसई

भविष्य में कुछ ऐसा देखना अच्छा होगा जैसे कि इकाई जो वास्तव में जौन के प्रकारों पर तालिका और दस्तावेज़ एनोटेशन का प्रतिनिधित्व करती है और मैं मक्खी पर CRUD सामान करने के लिए स्प्रिंग रिपॉजिटरी का उपयोग कर सकता हूं। सोचो कि मैं वसंत के साथ डेटाबेस के लिए काफी उन्नत REST एपीआई उत्पन्न कर रहा हूं। लेकिन JSON के साथ मैं काफी अप्रत्याशित ओवरहेड का सामना कर रहा हूं, इसलिए मुझे हर एक दस्तावेज को प्रश्नों के साथ उत्पन्न करने की आवश्यकता होगी।
केंसई

यदि JSON आपका एकल स्टोर है, तो आप MongoDB के साथ हाइबरनेट OGM का उपयोग कर सकते हैं।
व्लाद मिहालसीए

12

यदि कोई दिलचस्पी रखता है, तो आप हाइबरनेट के साथ जेपीए 2.1 @Convert/ @Converterकार्यक्षमता का उपयोग कर सकते हैं । हालाँकि आपको pgjdbc-ng JDBC ड्राइवर का उपयोग करना होगा । इस तरह आपको प्रति क्षेत्र में किसी भी प्रोप्राइटरी एक्सटेंशन, बोलियों और कस्टम प्रकारों का उपयोग करने की आवश्यकता नहीं है।

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

यह उपयोगी लगता है - JSON लिखने में सक्षम होने के लिए इसे किस प्रकार के बीच परिवर्तित करना चाहिए? यह है <MyCustomClass, स्ट्रिंग> या कुछ अन्य प्रकार?
मायर्सिया जूल

धन्यवाद - अभी सत्यापित किया गया है कि यह मेरे लिए काम करता है (JPA 2.1, हाइबरनेट 4.3.10, pgjdbc-ng 0.5, Postgres 9.3)
myrosia

क्या मैदान पर @ कॉलम (कॉलमडिफिनिशन = "जोंस") को निर्दिष्ट किए बिना इसे काम करना संभव है? हाइबरनेट इस परिभाषा के बिना एक varchar (255) बना रहा है।
tfranckiewicz

हाइबरनेट संभवतः यह नहीं जान सकता है कि आप वहां किस प्रकार का कॉलम चाहते हैं, लेकिन आप जोर देते हैं कि डेटाबेस स्कीमा को अपडेट करने के लिए हाइबरनेट की जिम्मेदारी है। इसलिए मुझे लगता है कि यह डिफ़ॉल्ट को चुनता है।
vasily

3

मुझे Postgres (javax.persistence.PersistenceException: org.hibernate.MappingException: JDBC प्रकार के लिए कोई बोली मैपिंग नहीं: 1111) देशी प्रश्नों (EntityManager के माध्यम से) के दौरान कोई समस्या नहीं थी (EntityManager के माध्यम से) जो कि एंटिटी क्लास में प्रोजेक्शन में जन्स फ़ील्ड्स को पुनः प्राप्त करते हैं। 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 प्रकार (ऊपर से, टिम फुलमर उत्तर) को लागू करने वाले वर्ग का वर्ग नाम है।


3

मैंने इंटरनेट पर पाए जाने वाले कई तरीकों की कोशिश की, उनमें से अधिकांश काम नहीं कर रहे हैं, उनमें से कुछ बहुत जटिल हैं। नीचे एक मेरे लिए काम करता है और अगर आप PostgreSQL प्रकार सत्यापन के लिए उस सख्त आवश्यकताओं की जरूरत नहीं है और अधिक सरल है।

PostgreSQL jdbc स्ट्रिंग प्रकार को अनिर्दिष्ट, जैसे <connection-url> jdbc:postgresql://localhost:test?stringtype=‌​unspecified </connect‌​ion-url>


धन्यवाद! मैं हाइबरनेट प्रकार का उपयोग कर रहा था, लेकिन यह आसान तरीका है! FYI यहाँ इस परम jdbc.postgresql.org/documentation/83/connect.html के
जेम्स

2

ऐसा करने के लिए एक आसान तरीका है जिसका उपयोग करके फ़ंक्शन बनाना शामिल नहीं है 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

धन्यवाद, इसका उपयोग varchar को ltree में डालने के लिए किया जाता है, पूरी तरह से काम करता है।
व्लादिमीर एम।

1

मैं इसमें भाग रहा था और कनेक्शन स्ट्रिंग के माध्यम से सामान को सक्षम नहीं करना चाहता था, और निहित रूपांतरणों की अनुमति देता था। पहले तो मैंने @ टाइप का उपयोग करने की कोशिश की, लेकिन क्योंकि मैं JSON से मैप / सीरियल करने के लिए कस्टम कन्वर्टर का उपयोग कर रहा हूं, तो मैं @ टाइप एनोटेशन लागू नहीं कर सकता। यह बताता है कि मुझे अपने @ कॉलम के एनोटेशन में कॉलमडिफ़िन = "json" को निर्दिष्ट करने की आवश्यकता है।

@Convert(converter = HashMapConverter.class)
@Column(name = "extra_fields", columnDefinition = "json")
private Map<String, String> extraFields;

3
आपने इस HashMapConverter वर्ग को कहां परिभाषित किया है। यह कैसा दिखता है।
संदीप
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.