Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Korzystanie z kolumny Oracle XMLType w trybie hibernacji

Mój kierunek i wymagania

  • Jednostka powinna przechowywać XML jako ciąg znaków (java.lang.String)
  • Baza danych powinna utrzymywać XML w kolumnie XDB.XMLType
    • Pozwala na indeksowanie i wydajniejsze zapytania typu xpath/ExtractValue/xquery
  • Skonsoliduj kilkanaście częściowych rozwiązań, które znalazłem w ciągu ostatniego tygodnia
  • Środowisko pracy
    • Oracle 11g r2 x64
    • Hibernacja 4.1.x
    • Java 1.7.x x64
    • Windows 7 Pro x64

Rozwiązanie krok po kroku

Krok 1:Znajdź xmlparserv2.jar (~1350kb)

Ten plik jar jest wymagany do skompilowania kroku 2 i znajduje się w instalacjach Oracle tutaj:%ORACLE_11G_HOME%/LIB/xmlparserv2.jar

Krok 1.5:Znajdź xdb6.jar (~257kb)

Jest to krytyczne, jeśli używasz Oracle 11gR2 11.2.0.2 lub nowszego lub przechowujesz jako BINARNY XML.

Dlaczego?

  • W wersji 11.2.0.2+ kolumna XMLType jest przechowywana przy użyciu SECUREFILE BINARYXML domyślnie, podczas gdy wcześniejsze wersje będą przechowywane jako BASICFILECLOB
  • Starsze wersje xdb*.jar nie dekodują poprawnie binarnego pliku XML i po cichu ulegają awarii
    • Sterowniki JDBC firmy Google Oracle Database 11g w wersji 2 i pobierz xdb6.jar
  • Diagnoza i rozwiązanie problemu dekodowania binarnego XML opisane tutaj

Krok 2:utwórz hibernację UserType dla kolumny XMLType

Dzięki Oracle 11g i Hibernate 4.x jest to łatwiejsze niż się wydaje.

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

Krok 3:Dodaj adnotację do pola w swojej jednostce.

Używam adnotacji ze sprężyną/hibernacją, a nie plików mapowania, ale wyobrażam sobie, że składnia będzie podobna.

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

Krok 4:Radzenie sobie z błędami appserver/junit w wyniku Oracle JAR

Po uwzględnieniu %ORACLE_11G_HOME%/LIB/xmlparserv2.jar (1350kb) w ścieżce klasy w celu rozwiązania błędów kompilacji, otrzymujesz teraz błędy wykonawcze z serwera aplikacji...

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

DLACZEGO BŁĘDY?

xmlparserv2.jar używa JAR Services API (Service Provider Mechanism) do zmiany domyślnych klas javax.xml używanych przez SAXParserFactory, DocumentBuilderFactory i TransformerFactory.

JAK TO SIĘ STAŁO?

javax.xml.parsers.FactoryFinder szuka niestandardowych implementacji, sprawdzając w tej kolejności zmienne środowiskowe, %JAVA_HOME%/lib/jaxp.properties, a następnie pliki konfiguracyjne w META-INF/services w ścieżce klasy, przed użyciem domyślne implementacje zawarte w JDK (com.sun.org.*).

Wewnątrz xmlparserv2.jar znajduje się katalog META-INF/services, który pobiera klasa javax.xml.parsers.FactoryFinder. Pliki są następujące:

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)

ROZWIĄZANIE?

Przełącz wszystkie 3 z powrotem, w przeciwnym razie zobaczysz dziwne błędy.

  • javax.xml.parsers.* napraw widoczne błędy
  • javax.xml.transform.*naprawia bardziej subtelne błędy parsowania XML
    • w moim przypadku z konfiguracją Apache Commons czytanie/pisanie

SZYBKIE ROZWIĄZANIE do rozwiązania błędów uruchamiania serwera aplikacji:argumenty JVM

Aby zastąpić zmiany wprowadzone przez xmlparserv2.jar, dodaj następujące właściwości maszyny JVM do argumentów uruchamiania serwera aplikacji. Logika java.xml.parsers.FactoryFinder najpierw sprawdzi zmienne środowiskowe.

-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

Jeśli jednak uruchomisz przypadki testowe przy użyciu @RunWith(SpringJUnit4ClassRunner.class) lub podobnego, nadal wystąpi błąd.

LEPSZE ROZWIĄZANIE błędów uruchamiania serwera aplikacji ORAZ błędów przypadków testowych? 2 opcje

Opcja 1:Użyj argumentów JVM dla serwera aplikacji i instrukcji @BeforeClass dla swoich przypadków testowych

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

Jeśli masz dużo przypadków testowych, staje się to bolesne. Nawet jeśli umieścisz go w super.

Opcja 2:Utwórz własne pliki definicji usługodawcy w ścieżce klas kompilacji/wykonawstwa dla swojego projektu, które zastąpią te zawarte w xmlparserv2.jar

W projekcie maven spring nadpisz ustawienia xmlparserv2.jar, tworząc następujące pliki w katalogu %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)

Do plików tych odwołuje się zarówno serwer aplikacji (nie są wymagane argumenty JVM), jak i rozwiązują wszelkie problemy z testami jednostkowymi bez konieczności wprowadzania zmian w kodzie.

Gotowe.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 3 sposoby na zwrócenie strefy czasowej z wartości daty i godziny w Oracle

  2. Wyzwanie testowania bazy danych Oracle — Porównaj dane schematu

  3. Dodawanie nazwy schematu do encji w danych Spring?

  4. Łączenie się z bazą danych Oracle za pomocą usług integracji serwera SQL

  5. jak sprawdzić wszystkie ograniczenia na stole w oracle