Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Verwenden der Oracle XMLType-Spalte im Ruhezustand

Meine Richtung und Anforderungen

  • Entität sollte XML als String speichern (java.lang.String)
  • Datenbank sollte XML in einer XDB.XMLType-Spalte beibehalten
    • Ermöglicht Indizierung und effizientere xpath/ExtractValue/xquery-Abfragen
  • Konsolidieren Sie etwa ein Dutzend Teillösungen, die ich in der letzten Woche gefunden habe
  • Arbeitsumgebung
    • Oracle 11g r2 x64
    • Ruhezustand 4.1.x
    • Java 1.7.x x64
    • Windows 7 Pro x64

Schritt-für-Schritt-Lösung

Schritt 1:Finden Sie xmlparserv2.jar (~1350 KB)

Dieses JAR ist zum Kompilieren von Schritt 2 erforderlich und ist hier in Oracle-Installationen enthalten:%ORACLE_11G_HOME%/LIB/xmlparserv2.jar

Schritt 1.5:Suchen Sie xdb6.jar (~257 KB)

Dies ist entscheidend, wenn Sie Oracle 11gR2 11.2.0.2 oder höher verwenden oder als BINARY XML speichern.

Warum?

  • In 11.2.0.2+ wird die XMLType-Spalte mit SECUREFILE BINARYXML gespeichert standardmäßig, während frühere Versionen als BASICFILECLOB gespeichert werden
  • Ältere Versionen von xdb*.jar decodieren binäres XML nicht richtig und schlagen stillschweigend fehl
    • Google Oracle Database 11g Release 2 JDBC-Treiber und xdb6.jar herunterladen
  • Diagnose und Lösung für das Problem der binären XML-Decodierung hier beschrieben

Schritt 2:Erstellen Sie einen Benutzertyp für den Ruhezustand für die XMLType-Spalte

Mit Oracle 11g und Hibernate 4.x ist dies einfacher, als es sich anhört.

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) {}
        }
    }
}   

Schritt 3:Kommentieren Sie das Feld in Ihrer Entität.

Ich verwende Anmerkungen mit Spring/Hibernate, keine Mapping-Dateien, aber ich nehme an, dass die Syntax ähnlich sein wird.

@Type(type="your.custom.usertype.HibernateXMLType")
@Column(name="attribute_xml", columnDefinition="XDB.XMLTYPE")
private String attributeXml;

Schritt 4:Umgang mit Appserver-/Junit-Fehlern als Ergebnis von Oracle JAR

Nachdem Sie %ORACLE_11G_HOME%/LIB/xmlparserv2.jar (1350 KB) in Ihren Klassenpfad aufgenommen haben, um Kompilierungsfehler zu beheben, erhalten Sie jetzt Laufzeitfehler von Ihrem Anwendungsserver ...

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 ...

WARUM DIE FEHLER?

xmlparserv2.jar verwendet die JAR Services API (Service Provider Mechanism), um die standardmäßigen javax.xml-Klassen zu ändern, die für SAXParserFactory, DocumentBuilderFactory und TransformerFactory verwendet werden.

WIE IST DAS GESCHEHEN?

Der javax.xml.parsers.FactoryFinder sucht nach benutzerdefinierten Implementierungen, indem er in dieser Reihenfolge nach Umgebungsvariablen, %JAVA_HOME%/lib/jaxp.properties, und dann nach Konfigurationsdateien unter META-INF/services im Klassenpfad sucht, bevor er verwendet Standardimplementierungen, die im JDK enthalten sind (com.sun.org.*).

Innerhalb von xmlparserv2.jar existiert ein Verzeichnis META-INF/services, das die Klasse javax.xml.parsers.FactoryFinder aufnimmt. Die Dateien sind wie folgt:

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)

LÖSUNG?

Schalten Sie alle 3 zurück, sonst sehen Sie seltsame Fehler.

  • javax.xml.parsers.* behebt die sichtbaren Fehler
  • javax.xml.transform.* behebt subtilere XML-Parsing-Fehler
    • in meinem Fall mit Apache-Commons-Konfiguration Lesen/Schreiben

SCHNELLE LÖSUNG zur Behebung der Anwendungsserver-Startfehler:JVM-Argumente

Um die von xmlparserv2.jar vorgenommenen Änderungen außer Kraft zu setzen, fügen Sie die folgenden JVM-Eigenschaften zu den Startargumenten Ihres Anwendungsservers hinzu. Die java.xml.parsers.FactoryFinder-Logik überprüft zuerst die Umgebungsvariablen.

-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

Wenn Sie jedoch Testfälle mit @RunWith(SpringJUnit4ClassRunner.class) oder ähnlichem ausführen, tritt der Fehler weiterhin auf.

BESSERE LÖSUNG für Anwendungsserver-Startfehler UND Testfallfehler? 2 Optionen

Option 1:Verwenden Sie JVM-Argumente für den Anwendungsserver und @BeforeClass-Anweisungen für Ihre Testfälle

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");

Wenn Sie viele Testfälle haben, wird dies schmerzhaft. Auch wenn Sie es in ein Super stecken.

Option 2:Erstellen Sie Ihre eigenen Service-Provider-Definitionsdateien im Kompilierungs-/Laufzeit-Klassenpfad für Ihr Projekt, die die in xmlparserv2.jar enthaltenen überschreiben.

Überschreiben Sie in einem Maven-Spring-Projekt die xmlparserv2.jar-Einstellungen, indem Sie die folgenden Dateien im Verzeichnis %PROJECT_HOME%/src/main/resources erstellen:

%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)

Auf diese Dateien wird sowohl vom Anwendungsserver verwiesen (keine JVM-Argumente erforderlich) als auch alle Komponententestprobleme gelöst, ohne dass Codeänderungen erforderlich sind.

Fertig.