2012-07-20 101 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>