使用StaxEventItemWriter构建非平凡的XML文件

时间:2014-02-10 13:45:05

标签: java xml spring-batch jaxb2

我需要使用此结构将某些数据库表的内容转储到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 个答案:

答案 0 :(得分:2)

[取自spring.io forum]
这不是默认的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;
    }

}