30

Oracle XMLType 列を Hibernate エンティティ クラスにマップする必要があります。実装を含む実用的な(そして私はよく知られていると思います)ソリューションがありますUserType。ただし、Oracle xml パーサーをインポートする必要があり、多くの問題が発生するため、使用できません。
xml 列の値に文字列としてアクセスし、変換をエンティティを操作するコードに任せても問題ありませんが、データベースから値を読み取ってデータベースに書き込む方法が見つかりません。私がこれまでに試したこと:

  1. エンティティ クラスのプロパティを として宣言しますString。結果値は として読み取られnullます。プロパティがちょうどSerializableの場合、「デシリアライズできません」という例外が発生します。
  2. @Formula注釈 ( )を使用しCAST xmlCol as varchar2(1000)ます。結果 - 値は保存されません
  3. を使用し@Loaderて入れます。それは最も有望な試みでした-値は正常に読み取られ、保存されましたが、xml列を含むエンティティのコレクションをロードする場合、(基になるテーブルがedの場合、Hibernateはsqlを使用しません)。CASTSELECTnull@LoaderLEFT JOIN

私がうまくいくと信じているもう1つのアプローチは、xml列をString(書き込み用に)プラスして読み取り用のダミーフィールドを持つこと@Formulaです。しかし、私には汚いハックのように見えるので、やむを得ない場合を除き、そうしないことをお勧めします。

最後に、私ができる最後のことは、DBスキーマを変更することです(ビュー+トリガー、列のデータ型の変更など、1つ以上のオプションもあります)が、これも私にとっては良いオプションではありません.

私は何かを逃したのだろうか、それとも(3)を機能させる方法があるのだろうか?

4

5 に答える 5

16

私の方向性と要件

  • エンティティは XML を文字列 (java.lang.String) として格納する必要があります
  • データベースは XDB.XMLType 列に XML を永続化する必要があります
    • インデックス作成とより効率的な xpath/ExtractValue/xquery タイプのクエリを可能にします
  • 先週見つけた十数個の部分的な解決策を統合する
  • 作業環境
    • オラクル 11g r2 x64
    • 休止状態 4.1.x
    • Java 1.7.x x64
    • ウィンドウズ 7 プロ 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 以降を使用している場合、または BINARY XML として保存している場合に重要です。

なんで?

  • 11.2.0.2 以降では、XMLType 列はデフォルトでSECUREFILE BINARY XMLを使用して格納されますが、以前のバージョンではBASICFILE CLOBとして格納されます。
  • 古いバージョンの xdb*.jar は、バイナリ xml を適切にデコードせず、警告なしで失敗します
    • Google Oracle Database 11g Release 2 JDBC ドライバーと xdb6.jar のダウンロード
  • ここで概説されているバイナリ XML デコードの問題の診断と解決策

手順 2: XMLType 列の休止状態の UserType を作成する

Oracle 11g と Hibernate 4.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: Oracle JAR の結果としての 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 解析エラーを修正します
    • 私の場合、Apache Commons 構成の読み取り/書き込みを使用

アプリケーション サーバーの起動エラーを解決するクイック ソリューション: 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 引数は不要) によって参照され、コードを変更することなく単体テストの問題を解決します。

終わり。

于 2013-08-16T21:02:31.240 に答える
6

これにはさらに簡単な解決策があります。ColumnTransformer アノテーションを使用するだけです。

@ColumnTransformer(read = "to_clob(data)", write = "?")
@Column( name = "data", nullable = false, columnDefinition = "XMLType" )
private String data;`
于 2013-11-12T13:39:00.090 に答える