Min retning og krav
- Enheden skal gemme XML som en streng (java.lang.String)
- Databasen skal bestå XML i en XDB.XMLType-kolonne
- Tillader indeksering og mere effektive xpath/ExtractValue/xquery-forespørgsler
- Konsolider et dusin eller deromkring delløsninger, jeg fandt i løbet af den sidste uge
- Arbejdsmiljø
- Oracle 11g r2 x64
- Dvale 4.1.x
- Java 1.7.x x64
- Windows 7 Pro x64
Trin-for-trin løsning
Trin 1:Find xmlparserv2.jar (~1350kb)
Denne jar er påkrævet for at kompilere trin 2 og er inkluderet i oracle-installationer her:%ORACLE_11G_HOME%/LIB/xmlparserv2.jar
Trin 1.5:Find xdb6.jar (~257 kb)
Dette er afgørende, hvis du bruger Oracle 11gR2 11.2.0.2 eller nyere eller gemmer som BINÆR XML.
Hvorfor?
- I 11.2.0.2+ er XMLType-kolonnen gemt ved hjælp af SECUREFILE BINARYXML som standard, hvorimod tidligere versioner gemmes som en BASICFILECLOB
- Ældre versioner af xdb*.jar afkoder ikke binær xml korrekt og fejler lydløst
- Google Oracle Database 11g Release 2 JDBC-drivere og download xdb6.jar
- Diagnose og løsning for binært XML-afkodningsproblem skitseret her
Trin 2:Opret en brugertype i dvale for XMLType-kolonnen
Med Oracle 11g og Hibernate 4.x er dette nemmere, end det lyder.
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) {}
}
}
}
Trin 3:Anmærk feltet i din enhed.
Jeg bruger annoteringer med spring/dvale, ikke kortlægningsfiler, men jeg forestiller mig, at syntaksen vil være ens.
@Type(type="your.custom.usertype.HibernateXMLType")
@Column(name="attribute_xml", columnDefinition="XDB.XMLTYPE")
private String attributeXml;
Trin 4:Håndtering af appserver/junit-fejl som følge af Oracle JAR
Efter at have inkluderet %ORACLE_11G_HOME%/LIB/xmlparserv2.jar (1350kb) i din klassesti for at løse kompileringsfejl, får du nu runtime-fejl fra din applikationsserver...
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 ...
HVORFOR FEJLENE?
xmlparserv2.jar bruger JAR Services API (Service Provider Mechanism) til at ændre standard javax.xml klasserne, der bruges til SAXParserFactory, DocumentBuilderFactory og TransformerFactory.
HVORDAN FORGÅR DET?
javax.xml.parsers.FactoryFinder leder efter brugerdefinerede implementeringer ved at tjekke for, i denne rækkefølge, miljøvariabler %JAVA_HOME%/lib/jaxp.properties og derefter for konfigurationsfiler under META-INF/services på klassestien, før du bruger standardimplementeringer inkluderet i JDK (com.sun.org.*).
Inde i xmlparserv2.jar findes en META-INF/services-mappe, som javax.xml.parsers.FactoryFinder-klassen opfanger. Filerne er som følger:
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ØSNING?
Skift alle 3 tilbage, ellers vil du se mærkelige fejl.
- javax.xml.parsers.* rette de synlige fejl
- javax.xml.transform.*retter mere subtile XML-parsingsfejl
- i mit tilfælde med apache commons-konfiguration læsning/skrivning
HURTIG LØSNING til at løse applikationsserverens startfejl:JVM-argumenter
For at tilsidesætte ændringerne foretaget af xmlparserv2.jar skal du tilføje følgende JVM-egenskaber til dine applikationsserverstartargumenter. Java.xml.parsers.FactoryFinder-logikken vil først tjekke miljøvariabler.
-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
Men hvis du kører testcases ved hjælp af @RunWith(SpringJUnit4ClassRunner.class) eller lignende, vil du stadig opleve fejlen.
BEDRE LØSNING til applikationsserverens startfejl OG testcase-fejl? 2 muligheder
Mulighed 1:Brug JVM-argumenter til appserveren og @BeforeClass-sætninger til dine testcases
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");
Hvis du har mange testtilfælde, bliver dette smertefuldt. Også selvom du lægger den i en super.
Mulighed 2:Opret dine egne Service Provider-definitionsfiler i kompilerings-/runtime-klassestien til dit projekt, som vil tilsidesætte dem, der er inkluderet i xmlparserv2.jar
I et Maven Spring-projekt skal du tilsidesætte xmlparserv2.jar-indstillingerne ved at oprette følgende filer i mappen %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)
Disse filer refereres af både applikationsserveren (ingen JVM-argumenter påkrævet) og løser eventuelle enhedstestproblemer uden at kræve kodeændringer.
Færdig.