Ответ 1
Мое направление и требования
- Entity должен хранить XML как строку (java.lang.String)
- База данных должна сохранять XML в столбце XDB.XMLType
- Позволяет индексировать и более эффективные запросы типа xpath/ExtractValue/xquery
- Консолидируйте дюжину частичных решений, которые я нашел за последнюю неделю.
- Рабочая среда
- Oracle 11g r2 x64
- Hibernate 4.1.x
- Java 1.7.x x64
- Windows 7 Pro x64
Пошаговое решение
Шаг 1: Найдите xmlparserv2.jar(~ 1350kb)
Эта банка требуется для компиляции шага 2 и включена в инсталляцию оракула здесь: % ORACLE_11G_HOME%/LIB/xmlparserv2.jar
Шаг 1.5: Найдите xdb6.jar(~ 257kb)
Это очень важно, если вы используете Oracle 11gR2 11.2.0.2 или выше или сохраняете его как BINARY XML.
Почему?
- В 11.2.0.2+ столбец XMLType хранится с использованием SECUREFILE BINARY XML по умолчанию, тогда как более ранние версии будут сохранены как BASICFILE CLOB
- Старые версии xdb *.jar неправильно декодируют двоичный xml и терпят неудачу
- Google Oracle Database 11g Release 2 Драйверы JDBC и загрузка xdb6.jar
- Диагностика и решение проблемы двоичного XML-декодирования, описанной здесь
Шаг 2: Создайте спящий пользовательский тип для столбца XMLType
С Oracle 11g и Hibernate 4.x это проще, чем кажется.
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) {}
}
}
}
Шаг 3. Аннотирование поля в вашей сущности.
Я использую аннотации с spring/hibernate, а не файлы сопоставления, но я думаю, что синтаксис будет похож.
@Type(type="your.custom.usertype.HibernateXMLType")
@Column(name="attribute_xml", columnDefinition="XDB.XMLTYPE")
private String attributeXml;
Шаг 4: Работа с ошибками appserver/junit в результате использования Oracle JAR
После включения% ORACLE_11G_HOME%/LIB/xmlparserv2.jar(1350kb) в вашем пути к классам, чтобы решить ошибки компиляции, теперь вы получаете ошибки времени выполнения от вашего сервера приложений...
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 ...
ПОЧЕМУ ОШИБКИ?
В xmlparserv2.jar используется JAR Services API (механизм поставщика услуг) для изменения классов javax.xml по умолчанию, используемых для SAXParserFactory, DocumentBuilderFactory и TransformerFactory.
КАК ЭТО ПРОИСХОДИТ?
javax.xml.parsers.FactoryFinder ищет пользовательские реализации, проверяя в этом порядке переменные окружения,% JAVA_HOME%/lib/jaxp.properties, а затем для файлов конфигурации в META-INF/services на пути к классам, перед использованием реализаций по умолчанию, включенных в JDK (com.sun.org. *).
Внутри xmlparserv2.jar существует каталог META-INF/services, который получает класс javax.xml.parsers.FactoryFinder. Файлы выглядят следующим образом:
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)
SOLUTION?
Переключите все 3 назад, иначе вы увидите странные ошибки.
- javax.xml.parsers. * исправить видимые ошибки.
- javax.xml.transform. *
исправляет более тонкие ошибки синтаксического анализа XML
- в моем случае, с настройкой чтения/записи конфигурации apache commons
БЫСТРЫЙ РЕШЕНИЕ для решения ошибок запуска сервера приложений: аргументы JVM
Чтобы переопределить изменения, сделанные с помощью xmlparserv2.jar, добавьте следующие свойства JVM к аргументам запуска вашего сервера приложений. Логика java.xml.parsers.FactoryFinder сначала проверит переменные среды.
-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
Однако, если вы запускаете тестовые примеры, используя @RunWith (SpringJUnit4ClassRunner.class) или аналогичные, вы по-прежнему будете испытывать ошибку.
ЛУЧШЕЕ РЕШЕНИЕ к ошибкам запуска сервера приложений и ошибкам проверки ошибок? 2 варианта
Вариант 1: Используйте аргументы JVM для серверов приложений и операторов @BeforeClass для тестовых случаев
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");
Если у вас много тестовых случаев, это становится болезненным. Даже если вы положили его в супер.
Вариант 2. Создайте свои собственные файлы определения поставщика услуг в пути к компиляции /runtime для вашего проекта, который переопределит те, которые включены в xmlparserv2.jar
В проекте maven spring переопределите параметры xmlparserv2.jar, создав следующие файлы в каталоге% 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)
Эти файлы ссылаются как на сервер приложений (не требуются аргументы JVM), так и решают любые проблемы unit test, не требуя каких-либо изменений кода.
Готово.