Oracle
 sql >> Base de données >  >> RDS >> Oracle

Utilisation de la colonne Oracle XMLType en veille prolongée

Ma direction et mes exigences

  • L'entité doit stocker XML sous forme de chaîne (java.lang.String)
  • La base de données doit conserver XML dans une colonne XDB.XMLType
    • Permet l'indexation et des requêtes de type xpath/ExtractValue/xquery plus efficaces
  • Consolider une douzaine de solutions partielles que j'ai trouvées au cours de la semaine dernière
  • Environnement de travail
    • Oracle 11g r2 x64
    • Hibernation 4.1.x
    • Java 1.7.x x64
    • Windows 7 Professionnel x64

Solution étape par étape

Étape 1 :Recherchez xmlparserv2.jar (~1 350 ko)

Ce jar est requis pour compiler l'étape 2 et est inclus dans les installations d'Oracle ici :%ORACLE_11G_HOME%/LIB/xmlparserv2.jar

Étape 1.5 :Rechercher xdb6.jar (~257 kb)

Ceci est essentiel si vous utilisez Oracle 11gR2 11.2.0.2 ou une version ultérieure, ou si vous stockez au format BINARY XML.

Pourquoi ?

  • Dans 11.2.0.2+, la colonne XMLType est stockée à l'aide de SECUREFILE BINARYXML par défaut, alors que les versions antérieures seront stockées en tant que BASICFILECLOB
  • Les anciennes versions de xdb*.jar ne décodent pas correctement le xml binaire et échouent silencieusement
    • Google Pilotes JDBC Oracle Database 11g Release 2 et téléchargez xdb6.jar
  • Diagnostic et solution du problème de décodage XML binaire décrit ici

Étape 2 :Créer un type d'utilisateur en veille prolongée pour la colonne XMLType

Avec Oracle 11g et Hibernate 4.x, c'est plus simple qu'il n'y paraît.

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

Étape 3 :Annotez le champ dans votre entité.

J'utilise des annotations avec spring/hibernate, pas des fichiers de mappage, mais j'imagine que la syntaxe sera similaire.

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

Étape 4 : Traitement des erreurs de serveur d'applications/junit à la suite d'Oracle JAR

Après avoir inclus %ORACLE_11G_HOME%/LIB/xmlparserv2.jar (1350kb) dans votre classpath pour résoudre les erreurs de compilation, vous obtenez maintenant des erreurs d'exécution de votre serveur d'application...

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

POURQUOI LES ERREURS ?

Le xmlparserv2.jar utilise l'API JAR Services (Service Provider Mechanism) pour modifier les classes javax.xml par défaut utilisées pour SAXParserFactory, DocumentBuilderFactory et TransformerFactory.

COMMENT ÇA S'EST PASSÉ ?

Le javax.xml.parsers.FactoryFinder recherche des implémentations personnalisées en vérifiant, dans cet ordre, les variables d'environnement, %JAVA_HOME%/lib/jaxp.properties, puis les fichiers de configuration sous META-INF/services sur le chemin de classe, avant d'utiliser le implémentations par défaut incluses avec le JDK (com.sun.org.*).

À l'intérieur de xmlparserv2.jar existe un répertoire META-INF/services, que la classe javax.xml.parsers.FactoryFinder récupère. Les fichiers sont les suivants :

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)

UNE SOLUTION ?

Remettez les 3 en arrière, sinon vous verrez des erreurs étranges.

  • javax.xml.parsers.* corrige les erreurs visibles
  • javax.xml.transform.*corrige des erreurs d'analyse XML plus subtiles
    • dans mon cas, avec la configuration apache commons lire/écrire

SOLUTION RAPIDE pour résoudre les erreurs de démarrage du serveur d'application :Arguments JVM

Pour remplacer les modifications apportées par xmlparserv2.jar, ajoutez les propriétés JVM suivantes aux arguments de démarrage de votre serveur d'applications. La logique java.xml.parsers.FactoryFinder vérifiera d'abord les variables d'environnement.

-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

Cependant, si vous exécutez des cas de test en utilisant @RunWith(SpringJUnit4ClassRunner.class) ou similaire, vous rencontrerez toujours l'erreur.

MEILLEURE SOLUTION aux erreurs de démarrage du serveur d'applications ET aux erreurs de cas de test ? 2 choix

Option 1 :Utilisez des arguments JVM pour le serveur d'applications et des instructions @BeforeClass pour vos scénarios de test

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

Si vous avez beaucoup de cas de test, cela devient pénible. Même si vous le mettez dans un supermarché.

Option 2 :Créez vos propres fichiers de définition de fournisseur de services dans le chemin de classe de compilation/d'exécution de votre projet, qui remplaceront ceux inclus dans xmlparserv2.jar

Dans un projet Maven Spring, remplacez les paramètres xmlparserv2.jar en créant les fichiers suivants dans le répertoire %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)

Ces fichiers sont référencés à la fois par le serveur d'application (aucun argument JVM requis) et résolvent tous les problèmes de test unitaire sans nécessiter de modification de code.

Terminé.