Создание нетривиального XML-файла с помощью StaxEventItemWriter
Мне нужно вывести содержимое некоторых таблиц базы данных в файл XML с этой структурой
<dump>
<table name="tableName1">
<records>
<record>
<first column name>value</first column name>
<second column name>value</second column name>
<third column name>value</third column name>
</record>
<record>...</record>
</records>
</table>
<table name="tableName2">...</table>
</dump>
Реальное количество записей для каждой таблицы неизвестно, поэтому я не могу сохранить все данные для одной таблицы в памяти и вывести ее в XML.
Моя работа сейчас определяется как:
<job id="dump-database-job">
<step id="dumpTables">
<tasklet>
<chunk reader="dumpReader" processor="dumpProcessor" writer="dumpWriter" commit-interval="100" />
</tasklet>
</step>
</job>
<bean name="dumpProcessor" class="RecordBeanToJaxbElementProcessor" />
<bean name="dumpReader" class="CompositeItemReader">
<property name="delegates">
<array>
<ref bean="TABLE_ONE_Reader" />
<ref bean="TABLE_TWO_Reader" />
<ref bean="TABLE_NTH_Reader" />
<!-- Other delegates omitted,one for table,for brevity... -->
</array>
</property>
<property name="name" value="dumpReader" />
</bean>
<bean name="TABLE_ONE_Reader" class="JdbcCursorItemReader">
<property name="rowMapper">
<bean name="rowMapper" class="RecordBeanRowMapper">
<property name="tableName=" value="TABLE_ONE" />
</bean>
</property>
<!--other mandatory property omitted -->
</bean>
<bean name="dumpWriter" class="StaxEventItemWriter" scope="step">
<property name="resource" value="file:#{jobParameters['outfile']}" />
<property name="shouldDeleteIfEmpty" value="true" />
<property name="marshaller" ref="marshaller" />
<property name="overwriteOutput" value="true" />
<property name="rootTagName" value="dump" />
</bean>
<bean name="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="supportJaxbElementClass" value="true" />
<property name="classesToBeBound">
<array>
<value>RecordBean</value>
</array>
</property>
</bean>
public class RecordBeanRowMapper implements RowMapper<RecordBean> {
final static RowMapper<Map<String, Object>> columnMapRowMapper = new ColumnMapRowMapper();
private String tableName;
public void setTableName(String tableName) {
this.tableName = tableName;
}
@Override
public RecordBean mapRow(ResultSet rs, int rowNum) throws SQLException {
final RecordBean b = new RecordBean();
b.setTableName(tableName);
b.setColumnValues(Maps.transformValues(columnMapRowMapper.mapRow(rs, rowNum), new Function<Object, String>() {
@Override
public String apply(Object input) {
return (input == null ? "NULL" : input.toString();
}
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(namespace="")
class RecordBean {
private String tableName;
@XmlJavaTypeAdapter
/* Entries are written using an adapter to write data as
* <key>value</key>
* <key>value</key>
* ...
*/
public Map<String,String> entries = new HashMap<String,String>();
}
/* Use to build item node name using dynamic tableName as
* <tableName>
* <key>value</key>
* <key>value</key>
* </tableName>
*/
public class RecordBeanToJaxbElementProcessor implements ItemProcessor<RecordBean, JAXBElement<?>> {
@Override
public JAXBElement<?> process(RecordBean item) throws Exception {
return new JAXBElement<RecordBean>(new QName(item.getTableName()), RecordBean.class, item);
}
}
Эта работа не завершена и не покрывает мои потребности, потому что результат выглядит как
<?xml version="1.0" encoding="UTF-8"?>
<dump>
<!-- First record of table TABLE_ONE -->
<TABLE_ONE>
<code>Code one</code>
<description>A record</description>
<agomappedtable>xyz</agomappedtable>
<enumcode>NULL</enumcode>
<is_persistent>false</is_persistent>
<keep_history_data>false</keep_history_data>
</TABLE_ONE>
<!-- Other tons from TABLE_ONE -->
<!-- First record of table TABLE_TWO -->
<TABLE_TWO>
<code>Code 2</code>
<description>Another record</description>
<his_name>no_name</his_name>
</TABLE_TWO>
<!-- Other tons from TABLE_TWO -->
<!-- More tables... -->
</dump>
Я думаю, что мне нужно обогатить компоненты писателя и / или маршаллера для достижения моей цели, но я не нашел хорошего способа продолжить:(
Мои вопросы:
Как построить сложную структуру XML, как описано в начале, и сделать работу полностью перезапускаемой и с небольшим использованием памяти?
1 ответ
[Взято с форума spring.io]
Это не возможно со стандартным StaxEventItemWriter. Мне пришлось сделать что-то подобное для одного из моих проектов и написал собственный GroupingStaxEventItemWriter. Смотрите код ниже. Вам нужно будет изменить метод openGroup и closeGroup для вашего конкретного случая использования. Обратите внимание, что тег группировки закрывается прямой записью в базовый java.io.Writer, а не через XMLEventWriter. Это требуется для перезапуска.
public class GroupingStaxEventItemWriter<T> extends StaxEventItemWriter<T> {
private static final String GROUP_IDENTIFIER = "CURRENT_GROUP";
private Classifier<T, String> classifier;
private String currentGroup;
private XMLEventWriter eventWriter;
private Writer writer;
@Override
public void write(List<? extends T> items) throws XmlMappingException, Exception {
Map<String, List<T>> itemsGroup = new LinkedHashMap<String, List<T>>();
for (T item : items) {
String group = classifier.classify(item);
if (!itemsGroup.containsKey(group)) {
itemsGroup.put(group, new ArrayList<T>());
}
itemsGroup.get(group).add(item);
}
for (String group : itemsGroup.keySet()) {
if (group == null || !group.equals(currentGroup)) {
if (currentGroup != null) {
closeGroup(currentGroup);
}
currentGroup = group;
if (currentGroup != null) {
openGroup(currentGroup);
}
}
super.write(itemsGroup.get(group));
}
}
protected void openGroup(String group) throws XMLStreamException, FactoryConfigurationError {
String groupTagName = group;
String groupTagNameSpacePrefix = "";
String groupTagNameSpace = null;
if (groupTagName.contains("{")) {
groupTagNameSpace = groupTagName.replaceAll("\\{(.*)\\}.*", "$1");
groupTagName = groupTagName.replaceAll("\\{.*\\}(.*)", "$1");
if (groupTagName.contains(":")) {
groupTagNameSpacePrefix = groupTagName.replaceAll("(.*):.*", "$1");
groupTagName = groupTagName.replaceAll(".*:(.*)", "$1");
}
}
XMLEventFactory xmlEventFactory = createXmlEventFactory();
eventWriter.add(xmlEventFactory.createStartElement(groupTagNameSpacePrefix, groupTagNameSpace, groupTagName));
}
protected void closeGroup(String group)
throws XMLStreamException, FactoryConfigurationError {
String groupTagName = group;
String groupTagNameSpacePrefix = "";
if (groupTagName.contains("{")) {
groupTagName = groupTagName.replaceAll("\\{.*\\}(.*)", "$1");
if (groupTagName.contains(":")) {
groupTagNameSpacePrefix = groupTagName.replaceAll("(.*):.*", "$1") + ":";
groupTagName = groupTagName.replaceAll(".*:(.*)", "$1");
}
}
try {
writer.write("</" + groupTagNameSpacePrefix + groupTagName + ">");
} catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to close group: [" + group + "]", ioe);
}
}
@Override
protected XMLEventWriter createXmlEventWriter(XMLOutputFactory outputFactory, Writer writer)
throws XMLStreamException {
this.writer = writer;
this.eventWriter = super.createXmlEventWriter(outputFactory, writer);
return eventWriter;
}
@Override
public void open(ExecutionContext executionContext) {
if (executionContext.containsKey(getExecutionContextKey(GROUP_IDENTIFIER))) {
currentGroup = executionContext.getString(getExecutionContextKey(GROUP_IDENTIFIER));
}
super.open(executionContext);
}
@Override
public void update(ExecutionContext executionContext) {
executionContext.putString(getExecutionContextKey(GROUP_IDENTIFIER), currentGroup);
super.update(executionContext);
}
@Override
public void close() {
if (currentGroup != null) {
try {
closeGroup(currentGroup);
} catch (XMLStreamException e) {
throw new ItemStreamException("Failed to write close tag for element: " + currentGroup, e);
} catch (FactoryConfigurationError e) {
throw new ItemStreamException("Failed to write close tag for element: " + currentGroup, e);
}
}
super.close();
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notNull(classifier, "Missing required property 'classifier'");
}
public void setClassifier(Classifier<T, String> classifier) {
this.classifier = classifier;
}
}