jjzjj

xml - 在 hibernate 中使用 Oracle XMLType 列

coder 2024-06-23 原文

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

  • 将实体类中的属性声明为 String .结果 - 值读取为 null .如果属性只是 Serializable ,我得到“无法反序列化”异常。
  • 使用 @Formula注释 ( CAST xmlCol as varchar2(1000) )。结果 - 值未存储
  • 使用 @Loader并放 CASTSELECT .这是最有希望的尝试 - 成功读取并存储了值,但是在加载包含 xml 列的实体集合时,我得到 null (如果基础表是 @Loader ed,Hibernate 不会在 LEFT JOIN 中使用 sql)。

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

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

    我想知道我是否遗漏了什么,或者有没有办法让 (3) 工作?

    最佳答案

    我的方向和要求

  • 实体应将 XML 存储为字符串 (java.lang.String)
  • 数据库应将 XML 保存在 XDB.XMLType 列中
  • 允许索引和更高效的 xpath/ExtractValue/xquery 类型查询
  • 整合我在上周找到的十几个部分解决方案
  • 工作环境
  • Oracle 11g r2 x64
  • hibernate 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,这很重要。

    为什么?
  • 在 11.2.0.2+ 中,XMLType 列使用 SECUREFILE BINARY 存储
    默认情况下 XML,而早期版本将存储为 BASICFILE
    CLOB
  • 旧版本的 xdb*.jar 无法正确解码二进制 xml 并静默失败
  • Google Oracle Database 11g 第 2 版 JDBC 驱动程序和下载 xdb6.jar
  • Diagnosis and solution for Binary XML decoding problem outlined here

  • 步骤 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.*
    修复了更微妙的 XML 解析错误
  • 就我而言,使用 apache commons 配置读取/写入

  • 解决应用服务器启动错误的快速解决方案:JVM Arguments

    要覆盖 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");
    

    如果你有很多测试用例,这会变得很痛苦。即使你把它放在一个 super 。

    选项 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 参数),并且无需更改任何代码即可解决任何单元测试问题。

    完毕。

    关于xml - 在 hibernate 中使用 Oracle XMLType 列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11578697/

    有关xml - 在 hibernate 中使用 Oracle XMLType 列的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    5. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

      我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

    随机推荐