在 Hibernate 中使用 Oracle XMLType 列

2024-01-02

我需要将 Oracle XMLType 列映射到 Hibernate 实体类。有一个有效的(我认为众所周知的)解决方案涉及实施UserType;但是,我无法使用它,因为需要导入 Oracle xml 解析器,这反过来会导致许多问题。
我可以将 xml 列的值作为字符串访问,并将转换留给操作实体的代码,但我找不到从数据库读取值并将其写入数据库的方法。到目前为止我已经尝试过:

  1. 将实体类中的属性声明为String。结果 - 值读为null。如果财产只是Serializable,我收到“无法反序列化”异常。
  2. Using @Formula注释(CAST xmlCol as varchar2(1000))。结果 - 值未存储
  3. Using @Loader并把CAST in SELECT。这是最有希望的尝试 - 值已成功读取并存储,但是当涉及到加载包含 xml 列的实体集合时,我得到null(Hibernate 不使用 sql@Loader如果基础表是LEFT JOINed).

我认为应该有效的另一种方法是将 xml 列设置为String(用于写入)加上用于读取的虚拟字段@Formula;然而,这对我来说似乎是一个肮脏的黑客行为,除非我别无选择,否则我宁愿不这样做。

最后,我能做的最后一件事是更改数据库架构(还有不止一个选项,如视图+触发器、列数据类型更改),但这对我来说也不是一个好的选择。

我想知道我是否错过了什么,或者也许有办法让(3)发挥作用?


我的方向和要求

  • 实体应将 XML 存储为字符串 (java.lang.String)
  • Database should persist XML in an XDB.XMLType column
    • 允许索引和更高效的 xpath/ExtractValue/xquery 类型查询
  • 整合我上周发现的十几个部分解决方案
  • Working Environment
    • 甲骨文 11g r2 x64
    • 休眠 4.1.x
    • Java 1.7.x x64
    • Windows 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,这一点至关重要。

Why?

  • 在 11.2.0.2+ 中,XMLType 列使用以下方式存储安全文件二进制 XML默认情况下,而早期版本将存储为基本文件 CLOB
  • Older versions of xdb*.jar do not properly decode binary xml and fail silently
    • Google Oracle 数据库 11g 第 2 版 JDBC 驱动程序并下载xdb6.jar
  • 此处概述的二进制 XML 解码问题的诊断和解决方案 https://stackoverflow.com/questions/18518569/retrieving-oracle-xmltype-stored-as-binary-xml-from-a-resultset-in-java

步骤 2:为 XMLType 列创建 hibernate 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 内部存在一个 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.* fixes more subtle XML parsing errors
    • 就我而言,与阿帕奇公共配置读/写

解决应用程序服务器启动错误的快速解决方案: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 参数),并且无需更改任何代码即可解决任何单元测试问题。

Done.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Hibernate 中使用 Oracle XMLType 列 的相关文章

随机推荐