XmlBeans 作為 Apache 提供的 XML-Java 綁定工具,通過 Schema 編譯生成強類型 Java 類,實現 XML 文檔的對象化操作。本文基於完整案例,演示 XmlBeans 核心 API 使用,並深度剖析 setNil() 刪除操作拋出 XmlValueNotNillableException 的根因與解決方案。
customers.xsd 描述客戶數據結構(含嵌套地址、主地址、賬單地址)。scomp 編譯生成類:scomp -out xmlbeans.jar customers.xsd。public class CustomerXmlBeans {
private final File xmlFile;
public CustomerXmlBeans(String filePath) { this.xmlFile = new File(filePath); }
// 讀取並遍歷
public void readCustomers() throws Exception {
CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
for (CustomerType customer : doc.getCustomers().getCustomerArray()) {
System.out.printf("ID:%s Name:%s %s Phone:%s%n",
customer.getId(), customer.getFirstname(), customer.getLastname(), customer.getPhoneNumber());
AddressType addr = customer.getAddress();
printAddress("Primary", addr.getPrimaryAddress());
printAddress("Billing", addr.getBillingAddress());
}
}
// 新建文檔並寫入
public void createCustomer() throws Exception {
CustomersDocument doc = CustomersDocument.Factory.newInstance();
CustomerType customer = doc.addNewCustomers().addNewCustomer();
customer.setId(3);
customer.setFirstname("Jessica");
customer.setLastname("Lim");
customer.setGender("female");
customer.setPhoneNumber("1234567");
AddressType addr = customer.addNewAddress();
PrimaryAddressType pAddr = addr.addNewPrimaryAddress();
pAddr.setPostalCode("350106");
pAddr.setAddressLine1("#25-1");
pAddr.setAddressLine2("SHINSAYAMA 2-CHOME");
BillingAddressType bAddr = addr.addNewBillingAddress();
bAddr.setReceiver("Ms Danielle");
bAddr.setPostalCode("350107");
bAddr.setAddressLine1("#167");
bAddr.setAddressLine2("NORTH TOWER HARBOUR CITY");
doc.save(xmlFile);
}
// 追加客戶
public void addCustomer() throws Exception {
CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
CustomerType customer = doc.getCustomers().addNewCustomer();
// ... 設置屬性同 createCustomer ...
doc.save(xmlFile);
}
// 刪除客戶(問題方法)
public void deleteCustomerById(long id) throws Exception {
CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
CustomerType[] customers = doc.getCustomers().getCustomerArray();
for (int i = 0; i < customers.length; i++) {
if (customers[i].getId() == id) {
customers[i].setNil(); // ❌ 拋出 XmlValueNotNillableException
break;
}
}
doc.save(xmlFile);
}
private void printAddress(String label, XmlObject addr) { /* ... */ }
}
Exception in thread "main" org.apache.xmlbeans.impl.values.XmlValueNotNillableException
at org.apache.xmlbeans.impl.values.XmlObjectBase.setNil(XmlObjectBase.java:624)
at com.CustomerXMLBean.deleteCustomer(CustomerXMLBean.java:167)
nillable="true"setNil() 語義是「將元素標記為 xsi:nil=true」,**前提是 Schema 中該元素聲明 nillable="true"**。
檢查生成的 CustomerType 接口/類,其對應的 Schema 片段應為:
<xs:element name="customer" type="CustomerType" maxOccurs="unbounded" nillable="true"/>
若原始 XSD 中 <xs:element name="customer" .../> **未聲明 nillable**,編譯生成的類不支持 setNil(),運行時拋出異常。
| 方案 | 操作 | 優缺點 |
|---|---|---|
| 方案一:修改 XSD 重新編譯(推薦) | 在 customers.xsd 中為 customer 元素添加 nillable="true",重新運行 scomp 覆蓋生成的 jar。 | 徹底解決,語義正確。需重新編譯部署。 |
| 方案二:物理移除節點(無需 XSD 變更) | 使用 DOM API 或 XmlBeans 低層 API 移除節點:customers[i].getDomNode().getParentNode().removeChild(customers[i].getDomNode()); | 快速修復,不改 Schema。繞過強類型約束,需小心命名空間。 |
| 方案三:邏輯刪除標記 | 在 CustomerType 中添加 deleted 布爾字段(需修改 XSD),設置 customer.setDeleted(true),查詢時過濾。 | 業務友好,支持恢復/審計。需 Schema 變更。 |
public void deleteCustomerPhysical(long id) throws Exception {
CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
CustomersDocument.Customers customersElem = doc.getCustomers();
CustomerType[] customers = customersElem.getCustomerArray();
for (int i = 0; i < customers.length; i++) {
if (customers[i].getId() == id) {
// 獲取底層 DOM 節點並移除
org.w3c.dom.Node domNode = customers[i].getDomNode();
domNode.getParentNode().removeChild(domNode);
break;
}
}
doc.save(xmlFile);
}
nillable="true"、minOccurs="0"。scomp 生成的 jar 納入版本控制或私有 Maven 倉庫,避免「本地能跑、CI 掛掉」。XmlValueNotNillableException 直接指向 Schema 缺陷,善用異常堆疊定位問題。XmlBeans 以強類型、XPath/XQuery 支持見長,但在「刪除操作需 Schema 配合」這一設計決策上容易踩坑。理解 nillable 與 setNil() 的綁定關係,掌握 DOM 級降級操作,能讓你在維護舊系統或特定場景下游刃有餘。