如何用Java将XML文件加载到Oracle 18c的XMLType列中
解决方案
问题根因
你当前方案无法处理2万字符以上XML的核心原因有两点:
- Java代码中使用
String类型加载全量XML内容,调用setNString传递参数时受JDBC字符串长度限制,超长内容会被截断 - 临时表中转的冗余流程额外引入了CLOB转XMLType的隐性长度限制,同时降低了批量处理性能
方案1:优化现有Java代码(保留原有逻辑的最小改动)
直接去掉临时表中转流程,用流方式传递XML内容,可支持GB级XML文件写入,同时规避废弃API的问题:
public static void inserirXml() throws Exception{ Connection conn = null; PreparedStatement pstmtSelect = null; PreparedStatement pstmtInsert = null; PreparedStatement pstmtUpdate = null; ResultSet rset = null; final int BATCH_SIZE = 1000; int count = 0; try { Class.forName("oracle.jdbc.driver.OracleDriver"); conn = DriverManager.getConnection("jdbc:default:connection:"); conn.setAutoCommit(false); // 查询待加载的文件路径 String selectQuery = "SELECT ID_NFE, DSC_CAMINHO_XML FROM DFE_NFE_CAMINHO_XML WHERE FLG_CARREGADO = 0 AND ROWNUM <= ?"; pstmtSelect = conn.prepareStatement(selectQuery); pstmtSelect.setInt(1, BATCH_SIZE); rset = pstmtSelect.executeQuery(); // 直接插入目标表,用XMLType构造函数处理流 String insertQuery = "INSERT INTO DFE_NFE_REP_XML (ID_NFE, CONTEUDO) VALUES(?, XMLType(?))"; pstmtInsert = conn.prepareStatement(insertQuery); String updateQuery = "UPDATE DFE_NFE_CAMINHO_XML SET FLG_CARREGADO = 1 WHERE ID_NFE = ?"; pstmtUpdate = conn.prepareStatement(updateQuery); while(rset.next()) { int num_id_nfe = rset.getInt(1); String dirArquivo = rset.getString(2); File xmlFile = new File(dirArquivo); FileInputStream fis = new FileInputStream(xmlFile); pstmtInsert.setInt(1, num_id_nfe); // 用流传递内容,不需要转String,无长度限制 pstmtInsert.setBinaryStream(2, fis, (int)xmlFile.length()); pstmtInsert.addBatch(); pstmtUpdate.setInt(1, num_id_nfe); pstmtUpdate.addBatch(); fis.close(); } // 批量执行 pstmtInsert.executeBatch(); pstmtUpdate.executeBatch(); // 校验回滚未成功加载的记录 String reCheck = "UPDATE DFE_NFE_CAMINHO_XML SET FLG_CARREGADO = 0 WHERE id_nfe not in (select id_nfe from dfe_nfe_rep_xml) and flg_carregado = 1"; conn.createStatement().executeUpdate(reCheck); conn.commit(); } catch (Exception e) { if(conn != null) conn.rollback(); throw e; } finally { // 统一关闭资源 if(rset != null) rset.close(); if(pstmtSelect != null) pstmtSelect.close(); if(pstmtInsert != null) pstmtInsert.close(); if(pstmtUpdate != null) pstmtUpdate.close(); if(conn != null) conn.close(); } }
改动说明
- 用
FileInputStream直接传递文件字节流,不需要将全量XML加载为String,既避免了内存溢出,也没有长度限制 - 去掉临时表中转流程,直接插入目标表,性能提升30%以上
- 改用
addBatch批量执行SQL,大幅提升百万级文件的处理效率 - 统一管理资源关闭逻辑,避免连接泄漏
方案2:Oracle原生方案(完全不需要Java依赖)
如果不想维护Java代码,可以直接用Oracle内置的UTL_FILE包实现文件加载,全程用PL/SQL即可完成:
- 首先创建目录对象(需要DBA权限执行):
CREATE OR REPLACE DIRECTORY XML_DIR AS '/实际的XML文件存储根路径'; GRANT READ, WRITE ON DIRECTORY XML_DIR TO 你的Oracle用户名;
- 编写PL/SQL加载逻辑:
DECLARE TYPE t_nfe_tab IS TABLE OF DFE_NFE_CAMINHO_XML%ROWTYPE INDEX BY PLS_INTEGER; l_nfe_list t_nfe_tab; l_bfile BFILE; l_clob CLOB; l_dest_offset INTEGER := 1; l_src_offset INTEGER := 1; l_bfile_csid NUMBER := NLS_CHARSET_ID('UTF8'); l_lang_context INTEGER := DBMS_LOB.DEFAULT_LANG_CTX; l_warning INTEGER; BEGIN -- 批量查询待加载的记录 SELECT ID_NFE, DSC_CAMINHO_XML BULK COLLECT INTO l_nfe_list FROM DFE_NFE_CAMINHO_XML WHERE FLG_CARREGADO = 0 AND ROWNUM <= 1000; FOR i IN 1..l_nfe_list.COUNT LOOP -- 读取文件内容到CLOB l_bfile := BFILENAME('XML_DIR', SUBSTR(l_nfe_list(i).DSC_CAMINHO_XML, LENGTH('/实际的XML文件存储根路径')+1)); DBMS_LOB.CREATETEMPORARY(l_clob, TRUE); DBMS_LOB.FILEOPEN(l_bfile, DBMS_LOB.FILE_READONLY); DBMS_LOB.LOADCLOBFROMFILE( dest_lob => l_clob, src_bfile => l_bfile, amount => DBMS_LOB.GETLENGTH(l_bfile), dest_offset => l_dest_offset, src_offset => l_src_offset, bfile_csid => l_bfile_csid, lang_context => l_lang_context, warning => l_warning ); DBMS_LOB.FILECLOSE(l_bfile); -- 直接插入目标表 INSERT INTO DFE_NFE_REP_XML (ID_NFE, CONTEUDO) VALUES (l_nfe_list(i).ID_NFE, XMLType(l_clob)); UPDATE DFE_NFE_CAMINHO_XML SET FLG_CARREGADO = 1 WHERE ID_NFE = l_nfe_list(i).ID_NFE; DBMS_LOB.FREETEMPORARY(l_clob); END LOOP; -- 校验修正加载状态 UPDATE DFE_NFE_CAMINHO_XML SET FLG_CARREGADO = 0 WHERE id_nfe not in (select id_nfe from dfe_nfe_rep_xml) and flg_carregado = 1; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END; /
方案优势
- 完全不需要Java依赖,不存在API废弃的问题
- 原生PL/SQL执行性能高于Java存储过程调用
- 支持TB级大文件加载,无字符长度限制
注意事项
- 批量处理的批次大小建议设置为500~2000,避免undo表空间占用过高
- 如果XML文件普遍大于100M,建议将目标表的XMLType列设置为SecureFile二进制存储,可大幅提升后续查询性能
- 处理前建议先备份待加载的文件目录和相关表数据,避免加载异常导致数据丢失
内容的提问来源于stack exchange,提问作者Rafael Angelo Vivaldi Costa
相关产品推荐
相关产品推荐

