我需要使用这种结构将一些数据库表的内容转储到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结构,并使作业完全可重新启动并且占用较少的内存?
摘自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; } }