sql >> データベース >  >> RDS >> Oracle

HibernateでのOracleXMLType列の使用

    私の方向性と要件

    • エンティティはXMLを文字列(java.lang.String)として保存する必要があります
    • データベースはXMLをXDB.XMLType列に永続化する必要があります
      • インデックス作成とより効率的なxpath/ExtractValue/xqueryタイプのクエリを可能にします
    • 先週見つけた12個ほどの部分的なソリューションを統合します
    • 作業環境
      • Oracle 11g r2 x64
      • Hibernate 4.1.x
      • Java 1.7.x x64
      • Windows 7 Pro x64

    ステップバイステップのソリューション

    ステップ1:xmlparserv2.jar(〜1350kb)を見つける

    このjarは、ステップ2をコンパイルするために必要であり、次のOracleインストールに含まれています:%ORACLE_11G_HOME%/ LIB / xmlparserv2.jar

    ステップ1.5:xdb6.jar(〜257kb)を見つける

    これは、Oracle 11gR2 11.2.0.2以降を使用している場合、またはBINARYXMLとして保存している場合に重要です。

    なぜですか?

    • 11.2.0.2+では、XMLType列は SECUREFILE BINARYXMLを使用して保存されます。 デフォルトでは、以前のバージョンは BASICFILECLOBとして保存されます。
    • 古いバージョンのxdb*.jarは、バイナリxmlを適切にデコードせず、サイレントに失敗します
      • Google OracleDatabase11gリリース2JDBCドライバー xdb6.jarをダウンロードします
    • ここで概説されているバイナリXMLデコード問題の診断と解決策

    ステップ2:XMLType列の休止状態のUserTypeを作成する

    Oracle11gとHibernate4.xを使用すると、これは思ったよりも簡単です。

    public class HibernateXMLType implements UserType, Serializable {
    static Logger logger = Logger.getLogger(HibernateXMLType.class);
    
    
    private static final long serialVersionUID = 2308230823023l;
    private static final Class returnedClass = String.class;
    private static final int[] SQL_TYPES = new int[] { oracle.xdb.XMLType._SQL_TYPECODE };
    
    @Override
    public int[] sqlTypes() {
        return SQL_TYPES;
    }
    
    @Override
    public Class returnedClass() {
        return returnedClass;
    }
    
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == null && y == null) return true;
        else if (x == null && y != null ) return false;
        else return x.equals(y);
    }
    
    
    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }
    
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
    
        XMLType xmlType = null;
        Document doc = null;
        String returnValue = null;
        try {
            //logger.debug("rs type: " + rs.getClass().getName() + ", value: " + rs.getObject(names[0]));
            xmlType = (XMLType) rs.getObject(names[0]);
    
            if (xmlType != null) {
                returnValue = xmlType.getStringVal();
            }
        } finally {
            if (null != xmlType) {
                xmlType.close();
            }
        }
        return returnValue;
    }
    
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
    
        if (logger.isTraceEnabled()) {
            logger.trace("  nullSafeSet: " + value + ", ps: " + st + ", index: " + index);
        }
        try {
            XMLType xmlType = null;
            if (value != null) {
                xmlType = XMLType.createXML(getOracleConnection(st.getConnection()), (String)value);
            }
            st.setObject(index, xmlType);
        } catch (Exception e) {
            throw new SQLException("Could not convert String to XML for storage: " + (String)value);
        }
    }
    
    
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        if (value == null) {
            return null;
        } else {
            return value;
        }
    }
    
    @Override
    public boolean isMutable() {
        return false;
    }
    
    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        try {
            return (Serializable)value;
        } catch (Exception e) {
            throw new HibernateException("Could not disassemble Document to Serializable", e);
        }
    }
    
    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
    
        try {
            return (String)cached;
        } catch (Exception e) {
            throw new HibernateException("Could not assemble String to Document", e);
        }
    }
    
    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
    
    
    
    private OracleConnection getOracleConnection(Connection conn) throws SQLException {
        CLOB tempClob = null;
        CallableStatement stmt = null;
        try {
            stmt = conn.prepareCall("{ call DBMS_LOB.CREATETEMPORARY(?, TRUE)}");
            stmt.registerOutParameter(1, java.sql.Types.CLOB);
            stmt.execute();
            tempClob = (CLOB)stmt.getObject(1);
            return tempClob.getConnection();
        } finally {
            if ( stmt != null ) {
                try {
                    stmt.close();
                } catch (Throwable e) {}
            }
        }
    }   
    

    ステップ3:エンティティのフィールドに注釈を付けます。

    マッピングファイルではなく、Spring / Hibernateでアノテーションを使用していますが、構文は似ていると思います。

    @Type(type="your.custom.usertype.HibernateXMLType")
    @Column(name="attribute_xml", columnDefinition="XDB.XMLTYPE")
    private String attributeXml;
    

    ステップ4:OracleJARの結果としてのappserver/junitエラーへの対処

    コンパイルエラーを解決するためにクラスパスに%ORACLE_11G_HOME%/ LIB / xmlparserv2.jar(1350kb)を含めた後、アプリケーションサーバーからランタイムエラーが発生するようになりました...

    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd<Line 43, Column 57>: XML-24509: (Error) Duplicated definition for: 'identifiedType'
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd<Line 61, Column 28>: XML-24509: (Error) Duplicated definition for: 'beans'
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd<Line 168, Column 34>: XML-24509: (Error) Duplicated definition for: 'description'
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd<Line 180, Column 29>: XML-24509: (Error) Duplicated definition for: 'import'
    ... more ...
    

    エラーが発生する理由

    xmlparserv2.jarは、JARサービスAPI(サービスプロバイダーメカニズム)を使用して、SAXParserFactory、DocumentBuilderFactory、およびTransformerFactoryに使用されるデフォルトのjavax.xmlクラスを変更します。

    どのように発生しましたか?

    javax.xml.parsers.FactoryFinderは、環境変数%JAVA_HOME%/ lib / jaxp.propertiesをこの順序でチェックし、次にクラスパスのMETA-INF / servicesの下にある構成ファイルをチェックして、カスタム実装を探します。 JDK(com.sun.org。*)に含まれているデフォルトの実装。

    xmlparserv2.jar内には、javax.xml.parsers.FactoryFinderクラスが取得するMETA-INF/servicesディレクトリがあります。ファイルは次のとおりです。

    META-INF/services/javax.xml.parsers.DocumentBuilderFactory (which defines oracle.xml.jaxp.JXDocumentBuilderFactory as the default)
    META-INF/services/javax.xml.parsers.SAXParserFactory (which defines oracle.xml.jaxp.JXSAXParserFactory as the default)
    META-INF/services/javax.xml.transform.TransformerFactory (which defines oracle.xml.jaxp.JXSAXTransformerFactory as the default)
    

    解決策?

    3つすべてを元に戻します。そうしないと、奇妙なエラーが表示されます。

    • javax.xml.parsers。*目に見えるエラーを修正します
    • javax.xml.transform。*より微妙なXML解析エラーを修正します
      • 私の場合、apachecommons構成 読み取り/書き込み

    アプリケーションサーバーの起動エラーを解決するためのクイックソリューション:JVM引数

    xmlparserv2.jarによって行われた変更を上書きするには、次のJVMプロパティをアプリケーションサーバーの起動引数に追加します。 java.xml.parsers.FactoryFinderロジックは、最初に環境変数をチェックします。

    -Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl -Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl -Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
    

    ただし、@ RunWith(SpringJUnit4ClassRunner.class)などを使用してテストケースを実行すると、エラーが発生します。

    アプリケーションサーバーの起動エラーとテストケースエラーに対するより良い解決策はありますか? 2つのオプション

    オプション1:アプリサーバーにはJVM引数を使用し、テストケースには@BeforeClassステートメントを使用します

    System.setProperty("javax.xml.parsers.DocumentBuilderFactory","com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
    System.setProperty("javax.xml.parsers.SAXParserFactory","com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
    System.setProperty("javax.xml.transform.TransformerFactory","com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
    

    テストケースがたくさんあると、これは苦痛になります。スーパーに入れても。

    オプション2:プロジェクトのコンパイル/ランタイムクラスパスに独自のサービスプロバイダー定義ファイルを作成します。これにより、xmlparserv2.jarに含まれているものが上書きされます

    Maven Springプロジェクトでは、%PROJECT_HOME%/ src / main /resourcesディレクトリに次のファイルを作成してxmlparserv2.jar設定をオーバーライドします。

    %PROJECT_HOME%/src/main/resources/META-INF/services/javax.xml.parsers.DocumentBuilderFactory (which defines com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl as the default)
    %PROJECT_HOME%/src/main/resources/META-INF/services/javax.xml.parsers.SAXParserFactory (which defines com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl as the default)
    %PROJECT_HOME%/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory (which defines com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl as the default)
    

    これらのファイルは両方のアプリケーションサーバーによって参照され(JVM引数は不要)、コードを変更することなく単体テストの問題を解決します。

    完了しました。



    1. MySQL IFNULL()の説明

    2. PostgreSQL ROLE(ユーザー)が存在しない場合は作成します

    3. 'where value in ...'句でパラメータを使用する方法は?

    4. T-SQL-=とasを使用したエイリアシング