在数据交互场景中,XML格式至今仍被大量系统使用,比如银行接口、企业级报文、配置文件等。这些XML文档往往不是扁平结构,而是存在明显的层级关系:一个订单节点下面挂着多个商品节点,一个班级节点下面包含多个学生节点。当我们需要把这类一对多结构的XML解析成Java对象时,就需要用到专门的映射技术。本文将分别介绍JAXB注解映射和MyBatis的collection映射两种主流方案,并给出完整的代码演示。

XML中一对多结构的常见形态
先来看一个典型的例子,假设有下面这样一份订单XML文档:
<order>
<orderId>1001</orderId>
<customer>张三</customer>
<items>
<item>
<name>机械键盘</name>
<price>399.00</price>
</item>
<item>
<name>无线鼠标</name>
<price>129.00</price>
</item>
</items>
</order>这份文档里,<order>是"一"的一方,<item>是"多"的一方,一个订单对应多个商品。这种结构有两种常见的表现形式:一种是像上面这样,多个重复节点被包裹在一个父容器节点<items>里;另一种是没有容器节点,多个<item>直接作为<order>的子节点重复出现。这两种形态在映射时的配置方式略有差别,后面会分别说明。
对应到Java对象层面,我们需要设计两个类:Order类持有一个List<Item>类型的属性,Item类则包含自己的字段。一对多映射的核心,就是告诉解析框架如何把XML中重复出现的节点逐一收集到这个List集合中。
使用JAXB注解完成一对多映射
JAXB是JDK自带的XML与Java对象互转框架(JDK 8及以前内置,JDK 9之后需要单独引入依赖),通过注解声明映射规则,无需手写解析逻辑。针对上面带容器节点的结构,实体类的写法如下:
import javax.xml.bind.annotation.*;
import java.util.List;
@XmlRootElement(name = "order")
@XmlAccessorType(XmlAccessType.FIELD)
public class Order {
@XmlElement(name = "orderId")
private Integer orderId;
@XmlElement(name = "customer")
private String customer;
@XmlElement(name = "items")
private Items items;
// 省略getter和setter
}
@XmlAccessorType(XmlAccessType.FIELD)
public class Items {
@XmlElement(name = "item")
private List<Item> itemList;
// 省略getter和setter
}
@XmlAccessorType(XmlAccessType.FIELD)
public class Item {
@XmlElement(name = "name")
private String name;
@XmlElement(name = "price")
private BigDecimal price;
// 省略getter和setter
}注意这里引入了一个中间类Items来对应容器节点,这是处理带包裹节点结构的标准做法。如果XML中没有<items>容器,多个<item>直接挂在<order>下面,那配置就简化了,直接在Order类中这样写:
@XmlElement(name = "item") private List<Item> itemList;
反序列化的代码非常简洁,几行就能把XML转成对象:
JAXBContext context = JAXBContext.newInstance(Order.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader reader = new StringReader(xmlString);
Order order = (Order) unmarshaller.unmarshal(reader);
// 遍历一对多集合
order.getItems().getItemList().forEach(item ->
System.out.println(item.getName() + ":" + item.getPrice()));除了@XmlElement,还有一种@XmlElementWrapper注解可以省掉中间类,它的作用正是为集合元素声明一个包裹节点:
@XmlElementWrapper(name = "items") @XmlElement(name = "item") private List<Item> itemList;
这样写就不需要单独定义Items类了,代码更加紧凑。JAXB同样支持把Java对象序列化回XML,调用Marshaller即可,双向转换能力是它的一大优势。缺点是注解配置相对繁琐,遇到层级很深的文档时,实体类的数量会随之膨胀。
MyBatis中collection标签处理一对多映射
另一种常见需求是把XML数据直接映射到MyBatis的查询结果中,或者理解MyBatis结果映射中一对多的处理思想。在MyBatis的Mapper XML里,<collection>标签专门负责一对多关系,它告诉框架某个属性是集合类型,需要按指定规则组装数据:
<resultMap id="orderResultMap" type="com.example.Order">
<id property="orderId" column="order_id"/>
<result property="customer" column="customer"/>
<collection property="items" ofType="com.example.Item">
<id property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>property指定Java类中的集合属性名,ofType指定集合中元素的类型,内层的<id>和<result>则完成元素内部字段与列的对应。需要注意<id>标签的作用很关键,MyBatis用它来区分不同的一对多子记录,如果漏写或者写错,可能出现集合元素重复或丢失的问题,这是实际开发中高频踩坑点。
当关联数据来自另一张表时,还可以用嵌套select的方式,为collection指定select属性指向一条子查询,框架会自动按外层主键去查询子记录并组装成集合。这种方式写起来简单,但存在N+1查询问题:外层查N条记录,就会触发N次子查询。数据量大时建议改用嵌套resultMap配合join查询,一次SQL把数据查全,性能差距会非常明显。
两种方案如何选择
JAXB适合处理独立的XML文档解析场景,比如接收第三方系统的XML报文、读取XML配置文件,它不依赖数据库,纯内存操作,转换速度快,而且支持双向转换。MyBatis的collection则面向数据库查询结果的组装,本质是ORM层面的关系映射,与XML数据解析属于不同的应用层面。
如果项目只是解析XML文件,首选JAXB或者更轻量的Dom4j手动解析;如果涉及分页读取超大XML文件,则可以考虑SAX或StAX流式解析,避免内存溢出。而当你需要在查询结果中体现一对多业务关系时,MyBatis的collection是标准答案。两者并不冲突,实际项目中经常同时使用:接口层用JAXB解析外部XML报文,落库和查询则交给MyBatis处理。
最后提醒一点,无论采用哪种方案,都要注意集合属性的初始化问题。JAXB在XML中对应节点不存在时会把集合置为null,直接遍历会抛空指针异常,建议在实体类中初始化为空的ArrayList,或者在业务代码里做好判空处理,养成这个习惯能避免不少线上问题。