基于 JAXB 注解方式解析 XML 文件与批量导入实现

基于 JAXB 注解方式解析 XML 文件与批量导入实现

本文以实际的项目需求为例,分享如何基于 JAXB 实现 XML 文件的解析,并将解析后的数据导入数据库

本文主要使用的 javax.xml 是 Java 自带的包,通常用来处理 XML 和 Java 对象之间的转换。具体来说,这些类属于 JAXB(Java Architecture for XML Binding),用于将 Java 对象序列化为 XML,或者将 XML 反序列化为 Java 对象。

需要注意:在 Java 9 及以后javax.xml.bind 被迁移到了模块化系统,并且从 JDK 11 开始 移除了 默认支持。如果你使用的 JDK 是 11 或以上版本,需要通过添加外部依赖来使用这些类,比如引入 javax.xml.bind 的实现库,例如:

Maven 依赖:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

或者将 com.sun.xml.bind 实现的库加入项目中:

<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.3.1</version>
</dependency>

如果使用的是 JDK 8,则这些类是默认包含在标准库中的,无需额外配置

一、XML 文件格式及对应的 JavaBean 设计

假设有以下 XML 文件内容:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Record>
    <RecordID>2921725428</RecordID>
    <RecordLastUpdateDate>20240204173652</RecordLastUpdateDate>
    <RecordTitle>String类型</RecordTitle>
    <Publication>
        <PublicationID>2026366</PublicationID>
        <Title>出版社题名</Title>
    </Publication>
    <AlphaPubDate>1976</AlphaPubDate>
    <NumericPubDate>19760101</NumericPubDate>
    <Contributor>
        <ContribRole>Author</ContribRole>
        <LastName>József</LastName>
        <FirstName>Terjéki</FirstName>
        <OriginalForm>József, Terjéki</OriginalForm>
    </Contributor>
    <Contributor>
        <ContribRole>Advisor</ContribRole>
        <LastName>Lajos</LastName>
        <FirstName>Pintér</FirstName>
        <OriginalForm>Lajos, Pintér</OriginalForm>
    </Contributor>
    <Contributor>
        <ContribRole>Advisor</ContribRole>
        <LastName>László</LastName>
        <FirstName>Hatvani</FirstName>
        <OriginalForm>László, Hatvani</OriginalForm>
    </Contributor>
    <Products>
        <Product>
            <ProductID>1006523</ProductID>
            <HasFullText>true</HasFullText>
        </Product>
        <Product>
            <ProductID>1007587</ProductID>
            <HasFullText>false</HasFullText>
        </Product>
        <Product>
            <ProductID>1007945</ProductID>
            <HasFullText>true</HasFullText>
        </Product>
    </Products>
    <Terms>
        <ClassTerm>
            <ClassTermType>DissSubject</ClassTermType>
            <ClassCode>0642</ClassCode>
            <ClassExpansion>Theoretical Mathematics</ClassExpansion>
        </ClassTerm>
        <ClassTerm>
            <ClassTermType>DissSubject</ClassTermType>
            <ClassCode>0405</ClassCode>
            <ClassExpansion>Mathematics</ClassExpansion>
        </ClassTerm>
        <GenSubjTerm>
            <GenSubjValue>Theoretical mathematics</GenSubjValue>
        </GenSubjTerm>
        <GenSubjTerm>
            <GenSubjValue>Applied mathematics</GenSubjValue>
        </GenSubjTerm>
        <FlexTerm>
            <FlexTermName>DissPaperKwd</FlexTermName>
            <FlexTermValue>Differential equation</FlexTermValue>
        </FlexTerm>
        <FlexTerm>
            <FlexTermName>DissPaperKwd</FlexTermName>
            <FlexTermValue>Non-equilibrium function </FlexTermValue>
        </FlexTerm>
    </Terms>
</Record>

1. Record

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cxstar.test.LocalDateAdapter;
import com.cxstar.test.LocalDateTimeAdapter;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Data
@XmlRootElement(name = "Record") // 定义 XML 根元素的名称为 <Record>
@XmlAccessorType(XmlAccessType.FIELD) // 指定通过字段映射到 XML
public class Record {

    /**
     * 映射为 XML 节点 <RecordID>
     * 示例:
     * <RecordID>2921725428</RecordID>
     */
    @XmlElement(name = "RecordID")
    private Long recordId;

    /**
     * 映射为 XML 节点 <RecordLastUpdateDate>
     * 使用 LocalDateTimeAdapter 自定义适配器处理 LocalDateTime 类型
     * 示例:
     * <RecordLastUpdateDate>20240204173652</RecordLastUpdateDate>
     */
    @XmlElement(name = "RecordLastUpdateDate")
    @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    private LocalDateTime recordLastUpdateDate;

    /**
     * 映射为 XML 节点 <RecordTitle>
     * 示例:
     * <RecordTitle>String类型</RecordTitle>
     */
    @XmlElement(name = "RecordTitle")
    private String recordTitle;

    /**
     * 映射为 XML 节点 <AlphaPubDate>
     * 示例:
     * <AlphaPubDate>1976</AlphaPubDate>
     */
    @XmlElement(name = "AlphaPubDate")
    private Integer alphaPubDate;

    /**
     * 映射为 XML 节点 <NumericPubDate>
     * 使用 LocalDateAdapter 自定义适配器处理 LocalDate 类型
     * 示例:
     * <NumericPubDate>19760101</NumericPubDate>
     */
    @XmlElement(name = "NumericPubDate")
    @XmlJavaTypeAdapter(LocalDateAdapter.class)
    private LocalDate numericPubDate;

    /**
     * 映射为 XML 节点 <Publication>
     * 该字段对应 RecordPublicationBean 类型
     * 示例:
     * <Publication>
     *     <PublicationID>2026366</PublicationID>
     *     <Title>出版社题名</Title>
     * </Publication>
     */
    @XmlElement(name = "Publication")
    private RecordPublicationBean publications;

    /**
     * 映射为多个 XML 节点 <Contributor>
     * 示例:
     * <Contributor>
     *     <ContribRole>Author</ContribRole>
     *     <LastName>József</LastName>
     *     <FirstName>Terjéki</FirstName>
     *     <OriginalForm>József, Terjéki</OriginalForm>
     * </Contributor>
     * <Contributor>
     *     <ContribRole>Advisor</ContribRole>
     *     <LastName>Lajos</LastName>
     *     <FirstName>Pintér</FirstName>
     *     <OriginalForm>Lajos, Pintér</OriginalForm>
     * </Contributor>
     */
    @XmlElement(name = "Contributor")
    private List<RecordContributorBean> contributors;

    /**
     * 映射为 XML 节点 <Products>,并包含多个 <Product> 子节点
     * 示例:
     * <Products>
     *     <Product>
     *         <ProductID>1006523</ProductID>
     *         <HasFullText>true</HasFullText>
     *     </Product>
     *     <Product>
     *         <ProductID>1007587</ProductID>
     *         <HasFullText>false</HasFullText>
     *     </Product>
     * </Products>
     */
    @XmlElementWrapper(name = "Products")
    @XmlElement(name = "Product") // 必须指定子节点的名称为 <Product>
    private List<RecordProductBean> products;

    /**
     * 映射为 XML 节点 <Terms>,包含多种类型的子节点
     * 子节点类型: <ClassTerm>, <GenSubjTerm>, <FlexTerm>
     * 示例:
     * <Terms>
     *     <ClassTerm>
     *         <ClassTermType>DissSubject</ClassTermType>
     *         <ClassCode>0642</ClassCode>
     *         <ClassExpansion>Theoretical Mathematics</ClassExpansion>
     *     </ClassTerm>
     *     <GenSubjTerm>
     *         <GenSubjValue>Theoretical mathematics</GenSubjValue>
     *     </GenSubjTerm>
     *     <FlexTerm>
     *         <FlexTermName>DissPaperKwd</FlexTermName>
     *         <FlexTermValue>Differential equation</FlexTermValue>
     *     </FlexTerm>
     * </Terms>
     */
    @TableField(exist = false) // MyBatis-Plus 标注,该字段不对应数据库表字段
    @XmlElementWrapper(name = "Terms")
    @XmlElements({
        @XmlElement(name = "ClassTerm", type = RecordTermsBean.class), // 映射为 <ClassTerm>
        @XmlElement(name = "GenSubjTerm", type = RecordTermsBean.class), // 映射为 <GenSubjTerm>
        @XmlElement(name = "FlexTerm", type = RecordTermsBean.class) // 映射为 <FlexTerm>
    })
    private List<RecordTermsBean> terms;
}

1.1 类级注解

  1. @XmlRootElement(name = “Record”)
    定义了当前类在生成 XML 文档时的根元素名称为 Record,即生成的 XML 中的最外层节点将被命名为 <Record>
  2. @XmlAccessorType(XmlAccessType.FIELD)
    指定了将类中的字段(field)直接映射为 XML 的节点或属性,而不是使用类的方法(如 getter 和 setter)

1.2 字段级注解

  1. @XmlElement(name = “RecordID”)
    将字段 recordId 映射为 XML 节点 <RecordID>

  2. @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    指定在处理 recordLastUpdateDate 字段时使用自定义适配器 LocalDateTimeAdapter,用于在序列化和反序列化 XML 时格式化 LocalDateTime 类型

    示例适配器 LocalDateTimeAdapter
    package com.test.util;
    
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
        private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    
        @Override
        public LocalDateTime unmarshal(String v) throws Exception {
            return (v == null || v.isEmpty()) ? null : LocalDateTime.parse(v, formatter);
        }
    
        @Override
        public String marshal(LocalDateTime v) throws Exception {
            return (v == null) ? null : v.format(formatter);
        }
    }
    
  3. @XmlElementWrapper
    用于将一个集合字段包裹在外层节点内,其功能是为集合字段生成一个外层的包装节点
    例如,当 products 是一个集合时:

    @XmlElementWrapper(name = "Products")
    @XmlElement(name = "Product")
    private List<RecordProductBean> products;
    
    • @XmlElementWrapper(name = “Products”):生成一个 <Products> 包装节点作为集合的外层。
    • @XmlElement(name = “Product”):指定集合中的每个元素用 <Product> 节点表示。
    示例 XML
    <Products>
        <Product>
            <ProductID>1006523</ProductID>
            <HasFullText>true</HasFullText>
        </Product>
        <Product>
            <ProductID>1007587</ProductID>
            <HasFullText>false</HasFullText>
        </Product>
    </Products>
    
    适用场景

    在 XML 表现中,集合字段需要一个外层节点包裹时,使用 @XmlElementWrapper 比直接使用 @XmlElement 更符合语义,且更易于生成符合要求的 XML

  4. @XmlElements
    当集合中的元素有多种类型时,@XmlElements 可以指定每种类型的对应节点名称。例如:

    @XmlElementWrapper(name = "Terms")
    @XmlElements({
        @XmlElement(name = "ClassTerm", type = RecordTermsBean.class),
        @XmlElement(name = "GenSubjTerm", type = RecordTermsBean.class),
        @XmlElement(name = "FlexTerm", type = RecordTermsBean.class)
    })
    private List<RecordTermsBean> terms;
    
    • @XmlElement(name = “ClassTerm”, type = RecordTermsBean.class):为 RecordTermsBean 类型的元素生成 <ClassTerm> 节点
    • @XmlElementWrapper(name = “Terms”):将集合包装在 <Terms> 节点内
    示例 XML
    <Terms>
        <ClassTerm>
            <ClassTermType>DissSubject</ClassTermType>
            <ClassCode>0642</ClassCode>
            <ClassExpansion>Theoretical Mathematics</ClassExpansion>
        </ClassTerm>
        <GenSubjTerm>
            <GenSubjValue>Theoretical mathematics</GenSubjValue>
        </GenSubjTerm>
    </Terms>
    

2. 子实体类设计

  • RecordPublicationBean:表示出版物信息
  • RecordContributorBean:表示贡献者信息
  • RecordProductBean:表示产品信息
  • RecordTermsBean:表示记录术语信息

示例:

package com.test.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@Data
@XmlRootElement(name = "Publication")
@XmlAccessorType(XmlAccessType.FIELD)
public class RecordPublicatio implements com.cxstar.common.entity.Bean {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.INPUT)
    private Long id;

    /**
     * 关联的记录ID
     */
    private Long recordId;

    /**
     * 出版物ID
     */
    @XmlElement(name = "PublicationID")
    private Long publicationId;

    /**
     * 出版物标题
     */
    @XmlElement(name = "Title")
    private String title;

}

二、基于 JAXB 的 XML 解析工具

为简化 XML 字符串与 Java 对象的转换,创建了一个通用的 XML 工具类 XmlUtil

package com.test.util;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;

/**
 * @author zhouquan
 * @date 2024年11月27日 14:07
 */
public class XmlUtil {

    /**
     * 将 XML 字符串转换为指定的 POJO 对象
     *
     * @param clazz  需要转换的类
     * @param xmlStr XML 数据
     * @return 转换后的对象
     * @throws JAXBException 如果 XML 解析失败
     */
    public static <T> T xmlStrToObject(Class<T> clazz, String xmlStr) throws JAXBException {
        // 校验输入参数
        if (clazz == null || xmlStr == null || xmlStr.trim().isEmpty()) {
            throw new IllegalArgumentException("输入的类或 XML 字符串不能为空");
        }

        // 创建 JAXB 上下文和反序列化器
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = context.createUnmarshaller();

        // 使用 try-with-resources 自动关闭资源
        try (StringReader reader = new StringReader(xmlStr)) {
            T result = (T) unmarshaller.unmarshal(reader);
            return result;
        }
    }

}

三、批量导入 XML 文件的实现

以下为批量解析并导入 XML 文件的核心方法:

@Override
public String importBatchXML(MultipartFile[] files) {
    if (files == null || files.length == 0) {
        log.warn("上传的文件列表为空");
        return "上传文件为空,请重新上传";
    }

    int successCount = 0;
    int totalFiles = files.length;

    for (MultipartFile file : files) {
        String fileName = file.getOriginalFilename();

        // 校验文件是否为 XML 格式
        if (fileName == null || !fileName.toLowerCase().endsWith(".xml")) {
            log.warn("文件 {} 不是有效的 XML 文件,已跳过", fileName);
            continue;
        }

        try (BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
            // 读取 XML 文件内容
            StringBuilder buffer = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                buffer.append(line);
            }

            // XML 转换为 Java 对象
            Record record = XmlUtil.xmlStrToObject(Record.class, buffer.toString());

            // 保存到数据库 
            recordService.save(recordBean);

            successCount++;
        } catch (Exception e) {
            log.error("解析文件 {} 失败", fileName, e);
        }
    }

    return String.format("总文件数:%d,成功导入数:%d", totalFiles, successCount);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐州蔡徐坤

又要到饭了兄弟们

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值