2012-07-20 65 views
27

我需要將Oracle XMLType列映射到hibernate實體類。有一個工作(我認爲是衆所周知的)解決方案涉及實施UserType;但是,我無法使用它,因爲需要導入Oracle XML解析器,這反過來會導致很多問題。
我很喜歡以字符串形式訪問xml列的值,並將轉換轉換爲操作實體的代碼,但我無法找到從中讀取值並將其寫入數據庫的方式。我迄今爲止嘗試:在實體類在休眠中使用Oracle XMLType列

  1. 聲明財產String。結果 - 值讀取爲null。如果財產只是Serializable,我得到「無法反序列化」的例外。
  2. 使用@Formula註釋(CAST xmlCol as varchar2(1000))。結果 - 值不存儲
  3. 使用@Loader並把CASTSELECT。這是最有前途的嘗試 - 值讀取和存儲成功,但是當涉及到包含XML列的實體裝載收集,我得到null(如果基礎表是LEFT JOIN版Hibernate不使用SQL在@Loader)。

另一種方法,我認爲應該工作是對XML列String(寫),加上空場與@Formula閱讀;然而,對我來說,它看起來像一個骯髒的黑客,我寧願不這樣做,除非我沒有選擇。

最後,最後我能做的事是改變DB模式(也多於1個選項,就像圖+觸發器,列數據類型的變化),但無論是不是對我來說是很好的選擇。

我不知道如果我錯過了什麼或可能有一種方法,使(3)工作?

+0

您是否嘗試過治療'XMLType'作爲'CLOB' ? – 2012-07-20 13:33:05

+0

我做到了。它引發一個異常。 – a1ex07 2012-07-20 14:16:43

+0

到目前爲止沒有解決? – Tarion 2013-01-31 12:36:39

回答

12

我的方向和要求

  • 實體應存儲XML作爲一個字符串(java.lang.String中)
  • 數據庫應在XDB.XMLType列XML堅持
    • 允許索引和更高效的xpath/ExtractValue/xquery類型查詢
  • 鞏固一打左右的部分解決方案,我發現過去一週
  • 工作環境
    • 的Oracle 11g R2 x64的
    • 休眠4.1.x的
    • 的Java 1.7.x 64
    • 的Windows 7專業版64位

分步解決方案

1步:找到xmlparserv2。罐(〜1350KB)

此jar需要編譯步驟2中,並且被包括在Oracle安裝位置: %ORACLE_11G_HOME%/ LIB/xmlparserv2.jar

步驟1.5:查找xdb6.jar( 〜257kb)

如果您使用的是Oracle 11gR2 11.2.0.2或更高版本,或者存儲爲BINARY XML,這一點至關重要。

爲什麼?

第2步:創建XMLType列

使用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:與在應用程序服務器/ junit的誤差作爲Oracle JAR

的結果處理包括在類路徑來解決編譯%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 Services API(服務提供者機制)來更改用於SAXParserFactory,DocumentBuilderFactory和TransformerFactory的默認javax.xml類。

它是如何發生的?

javax.xml.parsers.FactoryFinder通過依次檢查環境變量%JAVA_HOME%/ lib/jaxp.properties,然後查找META-INF/services下的配置文件來查找自定義實現。 classpath,在使用JDK包含的默認實現(com.sun.org。*)之前。

在xmlparserv2.jar裏面存在一個META-INF/services目錄,javax.xml.parsers.FactoryFinder類選擇這個目錄。文件如下:

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解析錯誤,在我的情況
    • ,與阿帕奇百科全書配置閱讀/寫入

QUICK方案來解決應用程序服務器啓動錯誤: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項目,覆蓋xmlparserv2.jar由%PROJECT_HOME%/ src目錄/主/資源目錄中創建下列文件中的設置:

%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

這個答案有最好的解釋(與Oracle XMLTYPE解析相關),我可以通過互聯網找到並且快速解決方案適用於我的問題。在我的情況下,我有tomcat7.0/lib文件夾中的xdb.jar和xmlparserv2.jar(都來自Oracle 11g安裝文件夾),這給我[此警告](http://stackoverflow.com/questions/16402786/can -not-permit-java-encoding-names-in-tomcat6)。男人,你搖滾! – yorkw 2013-08-28 01:44:13

+1

已更新,其中包含與從XMLType列中檢索通過java檢索數據相關的潛在問題/解決方案,這些列存儲爲BINARY XML,這是Oracle 11gR2 v11.2.0.2 +中的默認啓動項。 – 2013-08-29 20:28:40

5

存在一個更簡單的解決方案。只需使用ColumnTransformer註釋。

@ColumnTransformer(read = "to_clob(data)", write = "?") 
@Column(name = "data", nullable = false, columnDefinition = "XMLType") 
private String data;` 
+1

如果您存儲爲CLOB,那麼您是否仍然可以通過SQL執行xpath查詢? – 2015-02-04 20:54:52

+0

如果xml數據超過4000個字符,你會得到一個ORA錯誤 – Josh 2017-03-23 15:51:40

4

努力,沒有運氣許多不同的方法後,我想出了這個:

在我的實體類:

@ColumnTransformer(read = "NVL2(EVENT_DETAILS, (EVENT_DETAILS).getClobVal(), NULL)", write = "NULLSAFE_XMLTYPE(?)") 
@Lob 
@Column(name="EVENT_DETAILS") 
private String details; 

請注意圍繞 「EVENT_DETAILS」 括號。如果你不放它們,Hibernate將不會通過在左邊添加表名來重寫列名。

您將不得不創建NULLSAFE_XMLTYPE函數,該函數允許您插入空值(因爲在@ColumnTransformer上寫入轉換隻有一個問號的限制,而XMLType(NULL)會產生一個異常)。我創造了這樣的功能:

create or replace function NULLSAFE_XMLTYPE (TEXT CLOB) return XMLTYPE IS 
    XML XMLTYPE := NULL; 
begin 
    IF TEXT IS NOT NULL THEN 
     SELECT XMLType(TEXT) INTO XML FROM DUAL; 
    END IF; 

    RETURN XML; 
end; 

在我的persistence.xml文件:

<property name="hibernate.dialect" value="mypackage.CustomOracle10gDialect" /> 

定製的話(如果我們不覆蓋「useInputStreamToInsertBlob」的方法,我們會得到「ORA- 01461:只能用於插入綁定一個LONG值到LONG列」錯誤):

package mypackage; 

import org.hibernate.dialect.Oracle10gDialect; 

public class CustomOracle10gDialect extends Oracle10gDialect { 

    @Override 
    public boolean useInputStreamToInsertBlob() { 
     //This forces the use of CLOB binding when inserting 
     return false; 
    } 
} 

這是使用Hibernate 4.3.6和Oracle 11.2.0.1.0(含ojdbc6-11.1.0.7爲我工作。 0.jar)。

我不得不承認我沒有嘗試馬特M的解決方案,因爲它涉及很多黑客入侵併使用不在標準Maven存儲庫中的庫。

Kamuffel的解決方案是我的出發點,但是當我嘗試插入大型XML時出現ORA-01461錯誤,這就是爲什麼我必須創建自己的方言。此外,我發現TO_CLOB(XML_COLUMN)方法的問題(我會得到「ORA-19011:字符串緩衝區太小」的錯誤)。我想這樣XMLTYPE值首先被轉換爲VARCHAR2,然後轉換爲CLOB,因此在嘗試讀取大型XML時會導致問題。這就是爲什麼經過一番研究,我決定改用XML_COLUMN.getClobVal()。

我還沒有在互聯網上找到這個確切的解決方案。這就是爲什麼我決定創建一個StackOverflow帳戶來發布它,以便可以幫助別人。

我正在使用JAXB構造XML字符串,但我認爲它在這種情況下不相關。

4

爲了進一步簡化塞爾索的回答,人們可以通過避免使用Oracle的內置函數

XMLType.createxml創建一個自定義函數(?)

可以處理空值。

因此,以下注釋與Celso的自定義方言類相結合運作良好。

@Lob 
    @ColumnTransformer(read = "NVL2(EVENT_DETAILS, (EVENT_DETAILS).getClobVal(), NULL)", write = "XMLType.createxml(?)") 
    @Column(name = "EVENT_DETAILS") 
    private String details; 

您可能還需要在自定義方言中將clob註冊爲xmltype。所以,有效的您將有以下幾點:

public class OracleDialectExtension extends org.hibernate.dialect.Oracle10gDialect { 
    public OracleDialectExtension() { 
     super(); 
     registerColumnType(Types.CLOB, "xmltype"); 
    } 

    @Override 
    public boolean useInputStreamToInsertBlob() { 
     return false; 
    } 
} 

確保設置自定義的方言在Hibernate配置的會話工廠財產清單:

<property name="hibernate.dialect"><!-- class path to custom dialect class --></property>