sql >> Database >  >> RDS >> Oracle

Oracle XMLType-kolom in slaapstand gebruiken

Mijn richting en vereisten

  • Entiteit moet XML opslaan als een string (java.lang.String)
  • Database moet XML bevatten in een XDB.XMLType-kolom
    • Maakt indexering en efficiëntere xpath/ExtractValue/xquery-query's mogelijk
  • Consolideer een stuk of tien deeloplossingen die ik de afgelopen week heb gevonden
  • Werkomgeving
    • Oracle 11g r2 x64
    • Sluimerstand 4.1.x
    • Java 1.7.x x64
    • Windows 7 Pro x64

Stapsgewijze oplossing

Stap 1:zoek xmlparserv2.jar (~1350kb)

Deze jar is vereist om stap 2 te compileren en is hier opgenomen in oracle-installaties:%ORACLE_11G_HOME%/LIB/xmlparserv2.jar

Stap 1.5:zoek xdb6.jar (~257kb)

Dit is van cruciaal belang als u Oracle 11gR2 11.2.0.2 of hoger gebruikt of opslaat als BINARY XML.

Waarom?

  • In 11.2.0.2+ wordt de XMLType-kolom opgeslagen met SECUREFILE BINARYXML standaard, terwijl eerdere versies worden opgeslagen als een BASICFILECLOB
  • Oudere versies van xdb*.jar decoderen binaire xml niet goed en falen stil
    • Google Oracle Database 11g Release 2 JDBC-stuurprogramma's en download xdb6.jar
  • Diagnose en oplossing voor probleem met binaire XML-decodering hier geschetst

Stap 2:Maak een slaapstand UserType voor de XMLType-kolom

Met Oracle 11g en Hibernate 4.x is dit makkelijker dan het klinkt.

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

Stap 3:Annoteer het veld in uw entiteit.

Ik gebruik annotaties met spring/hibernate, geen mapping-bestanden, maar ik kan me voorstellen dat de syntaxis vergelijkbaar zal zijn.

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

Stap 4:Omgaan met de appserver/junit-fouten als gevolg van de Oracle JAR

Nadat u %ORACLE_11G_HOME%/LIB/xmlparserv2.jar (1350kb) in uw klassenpad heeft opgenomen om compileerfouten op te lossen, krijgt u nu runtime-fouten van uw toepassingsserver...

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

WAAROM DE FOUTEN?

De xmlparserv2.jar gebruikt de JAR Services API (Service Provider Mechanism) om de standaard javax.xml-klassen te wijzigen die worden gebruikt voor de SAXParserFactory, DocumentBuilderFactory en TransformerFactory.

HOE IS HET GEBEURD?

De javax.xml.parsers.FactoryFinder zoekt naar aangepaste implementaties door te controleren op, in deze volgorde, omgevingsvariabelen, %JAVA_HOME%/lib/jaxp.properties, en vervolgens op configuratiebestanden onder META-INF/services op het klassenpad, voordat de standaardimplementaties meegeleverd met de JDK (com.sun.org.*).

Binnen xmlparserv2.jar bestaat een map META-INF/services, die de klasse javax.xml.parsers.FactoryFinder oppikt. De bestanden zijn als volgt:

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)

OPLOSSING?

Schakel alle 3 terug, anders krijg je rare fouten te zien.

  • javax.xml.parsers.* herstel de zichtbare fouten
  • javax.xml.transform.*verhelpt subtielere XML-parseerfouten
    • in mijn geval, met apache commons-configuratie lezen/schrijven

SNELLE OPLOSSING om de opstartfouten van de applicatieserver op te lossen:JVM-argumenten

Om de wijzigingen die zijn aangebracht door xmlparserv2.jar te negeren, voegt u de volgende JVM-eigenschappen toe aan uw opstartargumenten voor de toepassingsserver. De logica van java.xml.parsers.FactoryFinder controleert eerst de omgevingsvariabelen.

-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

Als u echter testgevallen uitvoert met @RunWith(SpringJUnit4ClassRunner.class) of iets dergelijks, blijft de fout optreden.

BETERE OPLOSSING voor de opstartfouten van de toepassingsserver EN testcasefouten? 2 opties

Optie 1:gebruik JVM-argumenten voor de app-server en @BeforeClass-instructies voor uw testgevallen

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

Als je veel testgevallen hebt, wordt dit pijnlijk. Zelfs als je het in een super stopt.

Optie 2:Maak uw eigen Service Provider-definitiebestanden in het compile/runtime classpath voor uw project, die de bestanden in xmlparserv2.jar overschrijven

In een maven spring-project overschrijft u de xmlparserv2.jar-instellingen door de volgende bestanden te maken in de map %PROJECT_HOME%/src/main/resources:

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

Deze bestanden worden verwezen door zowel de applicatieserver (geen JVM-argumenten vereist) en lossen problemen met unittests op zonder dat er codewijzigingen nodig zijn.

Klaar.



  1. SQL voltooien. Verhalen van succes en mislukking

  2. Hoe kan ik de ORDER BY RAND()-functie van MySQL optimaliseren?

  3. Hoe DB_NAME() werkt in SQL Server

  4. SQL Server Hoge beschikbaarheid:SQL Server failover geclusterd exemplaar installeren, deel 2