解析xml數據存入bean映射到數據庫的 需求解決過程 2017年12月19日 15:18:57 守望dfdfdf 閱讀數:419 標簽: xmlbean 更多 個人分類: 工作 問題 編輯 版權聲明:本文為博主原創文章,轉載請注明文章鏈接。 https://blog.csdn.net/xiaoanzi123/article/details/78843037 首先貼上已知的一段代碼demo,解析xml。按照此方式進行功能開發。
package com.iflytek.zhejiang.hangzhou.qlsx.serviceimpl; import java.util.List; import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration; public class XMLParserSample { public static XMLParserSample getConf() { try { return parse();} catch (ConfigurationException e) {e.printStackTrace();} return null ;} private static XMLParserSample parse()
throws ConfigurationException {Configuration config =
new XMLConfiguration(XMLParserSample.
class .getClassLoader().getResource("conf.xml"
));String ip = config.getString("dataBaseURI"
);String account = config.getString("account"
);String password = config.getString("password"
);String runTime = config.getString("runTime"
);List <?> tasks = config.getList("tasks.table[@name]"
); // List<Table> tblLst = new ArrayList<Table>();
// for (int i = 0; i < tasks.size(); i++) {
//
// Table tbl = new Table();
// tbl.setName(config.getString(String.format(
// "tasks.table(%d)[@name]", i)));
// tbl.setInfo(config.getString(String.format(
// "tasks.table(%d)[@info]", i)));
// tbl.setPrimary(config.getString(String.format(
// "tasks.table(%d)[@primary]", i)));
// tbl.setColumns(config.getList(String.format(
// "tasks.table(%d).columns", i)));
// tbl.setTemplate(config.getString(String.format(
// "tasks.table(%d)[@template]", i)));
// tbl.setWaitTime(config.getString(String.format(
// "tasks.table(%d)[@waitTime]", i)));
// tbl.setRunTime(config.getString(String.format(
// "tasks.table(%d)[@runTime]", i)));
// tbl.setSingle(config.getString(String.format(
// "tasks.table(%d)[@single]", i)));
// tbl.setUrl(config.getString(String.format("tasks.table(%d).url", i)));
//
// if ( StringUtils.equals(TaskType.MULTI_GETTER, tbl.getTemplate())) {
// TableItem item = new TableItem();
//
// item.setName(config.getString(String.format(
// "tasks.table(%d).item[@name]", i)));
// item.setInfo(config.getString(String.format(
// "tasks.table(%d).item[@info]", i)));
// item.setRef(config.getString(String.format(
// "tasks.table(%d).item[@ref]", i)));
// item.setColumns(config.getList(String.format(
// "tasks.table(%d).item.columns", i)));
// item.setUrl(config.getString(String.format("tasks.table(%d).item.url", i)));
//
// tbl.setItem(item);
// }
//
// tblLst.add(tbl);
// }
//
// gc.setTblLst(tblLst);
//
// return gc;
// return null ;}
} 有以下幾點不理解: ①List<?> tasks = config.getList("tasks.table[@name]");是什么?tasks、table我猜測是conf.xml里面的開頭的標簽【根標簽附近的層級的標簽】名字,具體我也沒有conf.xml的內容格式,不知是啥。 XMLParserSample.class.getClassLoader().getResource("conf.xml") 這一串的意思像是利用了反射的知識內容,獲取解析器? ②Configuration 這種解析xml文件的方式我沒接觸過,之前只知道sax和dom4j。經過資料查詢,算是有所了解學習。參考 http://blog.csdn.net/t1dmzks/article/details/64500731 , Apache Commons Configuration 讀取xml文件的功能。參考:http://blog.csdn.net/qq_30739519/article/details/50865526 ③config.getString(String.format("tasks.table(%d)[@name]", i))); 這種寫法一方面是String的format方法的使用,相關知識可以參考 http://blog.csdn.net/lonely_fireworks/article/details/7962171/ 另一方面,結合②,[@name]這種語法是 XPATH的知識點。關于XPATH,我突然想起倆年前初學php入門時好像學過了解下,不過一直沒用到過,現在竟然忘得一干二凈認不出來,-_-||
demo中的實體bean Table類,demo中沒有對應的代碼,也沒有conf.xml文件,還有最后的那個gc,都不知道是干啥的,導致我很不理解疑問①的遍歷getList()到底是什么原理。一臉懵。循環體中無非就是從xml取出對應數據封裝到實體bean的屬性里面,dao層用的hibernate,但是demo里面沒有sql,所以我才猜測是利用的hibernate和數據庫表的映射關系來存儲數據的,個人也沒有學習過hibernate,只是有點接觸。最后返回的對象是什么gc是什么也是一頭霧水。以上為開發前對demo的整體感受簡述。
開發需求說明: AAAA表中,ROWGUID、UPDATE_DATE兩個字段作聯合主鍵(存在ROWGUID相同UPDATE_DATE不同的多條記錄),另有一個字段MATERIAL_INFO,存的就是下述xml內容的字符串形式的數據。我的任務就是取出這三個字段的值,封裝到三個新建表【建表語句在下面】對應的三個java bean里面,然后映射數據入庫。(多條記錄ROWGUID相同就取UPDATE_DATE最大的那條數據),另有過濾條件process=0,sql如下: select ROWGUID ,max(UPDATE_DATE) UPDATE_DATE,MATERIAL_INFO from AAAA where PROCESS = '0' group by ROWGUID "; 取出數據后,因為存放到三張表里面。表與表之間的字段關聯關系全在下面的建表語句的注釋中說明了(稍微有點復雜,熟悉了就好理解了) 貼上要開發分析的xml文件的內容格式:
<? xml version="1.0" encoding="utf-8" ?>
< DATAAREA > < CHANGE_FLAG > 1
</ CHANGE_FLAG > < MATERIALS > < MATERIAL > < MATERIALGUID > 2fadsfdsfsdc22ed9d
</ MATERIALGUID > < NAME > 水電費水電費水電費
</ NAME > < FORMAT > 1
</ FORMAT > < DETAIL_REQUIREMENT > 一份
</ DETAIL_REQUIREMENT > < NECESSITY > 1
</ NECESSITY > < NECESSITY_DESC /> < MATERIAL_RES /> < IS_RQ /> < EMPTYTABLE > < FILENAME /> < FILECONTENT /> < FILEURL /> </ EMPTYTABLE > < EXAMPLETABLE > < FILENAME /> < FILECONTENT /> < FILEURL /> </ EXAMPLETABLE > < BAK /> </ MATERIAL > < MATERIAL > < MATERIALGUID > 1fsdfdsfsd07a525a
</ MATERIALGUID > < NAME > 發送到發的所發生的
</ NAME > < FORMAT > 1
</ FORMAT > < DETAIL_REQUIREMENT > 1份
</ DETAIL_REQUIREMENT > < NECESSITY > 1
</ NECESSITY > < NECESSITY_DESC /> < MATERIAL_RES /> < IS_RQ /> < EMPTYTABLE > < FILENAME /> < FILECONTENT /> < FILEURL /> </ EMPTYTABLE > < EXAMPLETABLE > < FILENAME /> < FILECONTENT /> < FILEURL /> </ EXAMPLETABLE > < BAK /> </ MATERIAL > ......等多個 < MATERIAL ></ MATERIAL > </ MATERIALS >
</ DATAAREA > 新建三張表的建表語句:【表之間存在 邏輯外鍵的關系連接】
DROP TABLE IF EXISTS `material_info`;
CREATE TABLE `material_info` (
`MATERIALGUID` varchar (
255 )
NOT NULL COMMENT
' 主鍵 材料標識 ' ,
`QLSX_ROWGUID` varchar (
50 )
NOT NULL COMMENT
' AAAA 表中的rowguid對應的字段值 ' ,
`QLSX_UPDATE_DATE` datetime NOT NULL COMMENT
' AAAA 表中的update_date對應的字段值 ' ,
`NAME` varchar (
512 )
NOT NULL COMMENT
' 材料名稱 ' ,
`FORMAT` varchar (
8 ) COMMENT
' 材料形式,代碼項:材料形式 ' ,
`DETAIL_REQUIREMENT` varchar (
100 ) COMMENT
' 材料詳細要求 ' ,
`NECESSITY` varchar (
8 ) COMMENT
' 材料必要性,代碼項:材料必要性 ' ,
`NECESSITY_DESC` varchar (
255 ) COMMENT
' 材料必要性描述 ' ,
`IS_RQ` varchar (
255 ) COMMENT
'' ,
`BAK` varchar (
512 ) COMMENT
' 備注 ' ,
`UPDATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
PRIMARY KEY (`materialguid`)
) ENGINE = InnoDB
DEFAULT CHARSET
= utf8 COMMENT
= ' 權力事項庫-申報材料主表 ' ; DROP TABLE IF EXISTS `material_res`;
CREATE TABLE `material_res` (
`ID` bigint unsigned auto_increment COMMENT
' 物理主鍵,無意義 ' ,
`MATERIALGUID` varchar (
255 )
NOT NULL COMMENT
' 邏輯主鍵,參照material_info表中對應的materialguid字段值 ' ,
`MATERIAL_RES` varchar (
255 ) COMMENT
' 材料出具單位 ' ,
`EXAMPLETABLE` CHAR (
1 )
default ' 0 ' NOT NULL COMMENT
' FILENAME、FILECONTENT、FILEURL的xml中的父標簽是否為EXAMPLETABLE 1是 0不是 ' ,
`FILENAME` varchar (
255 ) COMMENT
' 空白表格文件名稱(完整文件名,帶后綴) ' ,
`FILECONTENT` longtext COMMENT ' 文件內容,BASE64編碼格式 ' ,
`FILEURL` varchar (
255 ) COMMENT
' 附件鏈接地址(公有云地址) ' ,
`UPDATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET
= utf8 COMMENT
= ' 權力事項庫-申報材料出具單位和材料信息表 ' ; DROP TABLE IF EXISTS `material_trans_log`;
CREATE TABLE `material_trans_log` (
`ID` bigint unsigned auto_increment COMMENT
' 物理主鍵,無意義 ' ,
`QLSX_ROWGUID` varchar (
50 )
NOT NULL COMMENT
' AAAA 表中的rowguid對應的字段值 ' ,
`QLSX_UPDATE_DATE` datetime NOT NULL COMMENT
' AAAA 表中的update_date對應的字段值 ' ,
`QLSX_CHANGE_FLAG` varchar (
10 )
NOT NULL COMMENT
' 0:未變化,1:變化 ' ,
`CREATE_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET
= utf8 COMMENT
= ' 權力事項庫-申報材料解析入庫的日志表 ' ;
?
取出三個字段的值,做參數傳給自己寫的xml解析方法parse();在解析方法中進行xml解析并進行對應bean的封裝。 經過請示可以不必非要按照demo,果斷改用dom4j開始解析賦值。自己的代碼如下【踩坑提示:在處理xml時,一定要注意標簽之間的層級關系以及數量關系,不然容易出現邏輯錯誤】】:
public XmlParserVO parse(String rowguid,Timestamp updatedate,String materialinfo)
throws DocumentException {
// 參數就是剛剛取出的三個字段的值
Document document = DocumentHelper.parseText(materialinfo);
// materialinfo 為xml格式字符串 // 一個xml就一個 CHANGE_FLAG 標簽,不在material標簽內部,故在遍歷前取出 String changeFlag = document.selectSingleNode("/DATAAREA/CHANGE_FLAG"
).getText(); // materialinfo MaterialInfo MaterialTransLog 為新建三張表對應的類bean List<MaterialRes> listRes =
new ArrayList<MaterialRes>
();List <MaterialInfo> listInfo =
new ArrayList<MaterialInfo>
();MaterialTransLog materialtrauslog =
new MaterialTransLog();materialtrauslog.setQLSX_ROWGUID(rowguid);materialtrauslog.setQLSX_UPDATE_DATE(updatedate);materialtrauslog.setQLSX_CHANGE_FLAG(changeFlag); // 獲取MATERIALS節點 Node nodeMaterials = document.selectSingleNode("/DATAAREA/MATERIALS"
); // 取得MATERIALS節點下所有名為“MATERIAL”的子節點,并進行遍歷. 遍歷后每一個元素就是一組 MATERIAL 標簽中的值 List listMaterial = ((Element) nodeMaterials).elements("MATERIAL"
); for (Iterator it =
listMaterial.iterator(); it.hasNext();) { Element elmMaterial = (Element) it.next();
// 獲取一個 MATERIAL 節點 // 提前取出materialRes、materialGuid,供下面material_res復用 String materialRes = elmMaterial.element("MATERIAL_RES"
).getText();String materialGuid = elmMaterial.element("MATERIALGUID"
).getText();MaterialInfo materialInfo =
new MaterialInfo();materialInfo.setMATERIALGUID(materialGuid);materialInfo.setQLSX_ROWGUID(rowguid);materialInfo.setQLSX_UPDATE_DATE(updatedate);materialInfo.setNAME(elmMaterial.element( "NAME"
).getText());materialInfo.setFORMAT(elmMaterial.element( "FORMAT"
).getText());materialInfo.setDETAIL_REQUIREMENT(elmMaterial.element( "DETAIL_REQUIREMENT"
).getText());materialInfo.setNECESSITY(elmMaterial.element( "NECESSITY"
).getText());materialInfo.setNECESSITY_DESC(elmMaterial.element( "NECESSITY_DESC"
).getText());materialInfo.setIS_RQ(elmMaterial.element( "IS_RQ"
).getText());materialInfo.setBAK(elmMaterial.element( "BAK"
).getText());listInfo.add(materialInfo); /* * EMPTYTABLE EXAMPLETABLE 標簽(可能有多個)下 均有FILENAME、FILECONTENT、FILEURL子標簽,* 會產生多個數據,遍歷后在每一個數據下再進行bean的創建封裝,以對應每一條記錄。 */ List listEmptytable = elmMaterial.elements("EMPTYTABLE"
); for (Iterator it2 =
listEmptytable.iterator(); it2.hasNext();) { Element elmEmptytable = (Element) it2.next();
// 獲取一個 EMPTYTABLE 節點 MaterialRes materialres =
new MaterialRes();materialres.setMATERIALGUID(materialGuid);materialres.setMATERIAL_RES(materialRes);materialres.setEXAMPLETABLE(EnumNumber.NUMZERO.getNumber()); // 枚舉類型 值為0 materialres.setFILENAME(elmEmptytable.element("elmEmptytable"
).getText());materialres.setFILECONTENT(elmEmptytable.element( "FILECONTENT"
).getText());materialres.setFILEURL(elmEmptytable.element( "FILEURL"
).getText());listRes.add(materialres);}List listExampletable = elmMaterial.elements("EXAMPLETABLE"
); for (Iterator it3 =
listExampletable.iterator(); it3.hasNext();) { Element elmExampletable = (Element) it3.next();
// 獲取一個 EXAMPLETABLE 節點 MaterialRes materialres =
new MaterialRes();materialres.setMATERIALGUID(materialGuid);materialres.setMATERIAL_RES(materialRes);materialres.setEXAMPLETABLE(EnumNumber.NUMONE.getNumber()); // 枚舉類型 值為1 materialres.setFILENAME(elmExampletable.element("elmEmptytable"
).getText());materialres.setFILECONTENT(elmExampletable.element( "FILECONTENT"
).getText());materialres.setFILEURL(elmExampletable.element( "FILEURL"
).getText());listRes.add(materialres);} } // 返回封裝好的數據集合 XmlParserVO xmlparservo =
new XmlParserVO(listRes, listInfo, materialtrauslog); return xmlparservo;
我把一些細節以及修改在代碼中用注釋進行了標注,以便于理解。 小細節: ①枚舉類型的使用,而不是直接給值1或者0,枚舉的好處在此不再贅述。【我也是被經理指出后的建議才修改的】,其實這里可以直接就在本類中定義常量的形式代替枚舉,因為別的地方也沒有用到。總之不要直接在代碼中去寫“0”這一類的代碼。目的是為了以后便與修改。 ②最后返回數據用 數據傳輸對象 封裝返回。 ③ 變量命名規范,我個人的壞毛病總是喜歡用下劃線,在這里強調下能用駝峰別用下劃線,推薦安裝阿里的java開發規范插件,代碼至少要做到工整,還有,注釋要完整,比如我經常會忘記在方法的注釋中只寫個簡潔的說明,而不寫參數@param等注釋,這樣很不規范嚴謹,給后來的開發者維護帶來困難。 對返回的集合遍歷取出bean,作為object參數傳入dao,進行數據庫插入:
[java] view plain copy
<code
class ="language-java">@SuppressWarnings("rawtypes"
)
public class GetMaterialInfoDao
extends HibernateEntityDao { private SessionFactory sessionFactory; public SessionFactory getSessionFactory() { return sessionFactory; } public void setSessionFactory(SessionFactory sessionFactory) { this .sessionFactory =
sessionFactory; super .sessionFactory =
sessionFactory; } @SuppressWarnings( "unchecked"
) @Transactional public void saveDate(Object obj){ Session session =
sessionFactory.getCurrentSession(); session.save(obj); } } </code>
這段代碼其實就是我不會用hibernate的體現。-save()方法還用在dao層自己寫嗎??? ̄□ ̄|| 功能邏輯上到此就完成了。但是因為一些細節,導致一直在不斷修改,也算是踩坑記錄吧。 1、當數據存入新建的表之后,要把 AAAA 表中已經取出并入庫的該條數據的process字段從0 改為1 ,來標志著該條數據記錄已經完成取值入庫的處理流程。一開始我是用rowguid和update_date兩個字段參數去 AAAA 表更新process字段,但是經理說我邏輯不對,為什么?我用做更新process字段傳遞的這兩個參數rowguid和update_date值,來自于最開始從 AAAA 中取回xml數據時的那個查詢語句,這個update_date 是取得最大值【max(UPDATE_DATE) UPDATE_DATE】,如果我還用這個update_date做參數去定位數據記錄,那么我只能更新 AAAA 表中該rowguid下rowguid相同,update_date最大的那條數據的process,其他的給漏掉了。比如:
--------------------------------------------------------- rowguid | update_date |process 12? ? | 2017-08-13 | 0 ---------------也應該改為1,但被漏掉了 12 ? | 2017-08-14 | 0 ---------------也應該改為1,但被漏掉了 12 ?? | 2017-08-15 | 0 ---------------改為 1
開發過程中考慮的不嚴謹不周全,【關于這一點,還有,在把解析出的數據存入庫之前,再做一個驗證,去material_info表里面查詢一下這條數據是否已經存在,如果已存在,刪了,再把現在剛解析出的數據插入進去,這一點也是我不曾考慮到的】,雖然對業務流程不熟悉,畢竟只是單一接到功能需求,大范圍業務流程背景并不清楚,但是嚴謹周全的工作思路還是必須的。不然就是一堆bug了。。。。 2、 在最開始從 AAAA 表中取數據時,內存溢出異常。三十萬條數據,我直接就一次取出放入list接收了,根本就沒有考慮可行的意識,只是直接認為這樣能實現功能,還是在實現功能的層面去開發。肯定要分頁去查詢了啊(mysql limit)。改進的過程中,先獲取總記錄數,定義一次查多少條,計算查詢出次數,然后分頁 limit 查詢 。再從另外一角度考慮下,為什么不在這里加一個多線程去執行,這樣不是更快嗎,現在肯定很慢。再次改進,代碼如下: ------------------------------------添加 線程 并 分頁 從 _new表 查詢 xml 等數據 代碼---------------------------------
private static final int LIMIT = 50
; private final static Executor executor = Executors.newCachedThreadPool();
// 啟用多線程
@Override public void dataHanding() {Long countNum =
qltQlsxDao.getCountNum(); int times = 0
; if ( 0 == countNum %
LIMIT ){times = (
int ) (countNum /
LIMIT);} else {times = (
int ) (countNum / LIMIT) +1
;} // int times = (int) Math.floor(countNum / LIMIT); // 分頁查詢 避免內存溢出 for (
int i = 1; i <= times; i++
) {
// List<QltQlsxNewDto> dataLst = qltQlsxDao.getMaterialInfo((i - 1) * LIMIT, LIMIT);
// if ( null == dataLst || dataLst.isEmpty() ) {
// logger.info("無數據需要處理");
// return ;
// } final int j=
i; executor.execute( new Runnable() {@Override public void run() { try {List <QltQlsxNewDto> dataLst = qltQlsxDao.getMaterialInfo((j - 1) *
LIMIT, LIMIT); if (
null == dataLst ||
dataLst.isEmpty() ) {logger.info( "無數據需要處理"
); return ;}parseXMLInfoAndSave(dataLst);} catch (Exception e){}}}); }} 經過實際測試,速度比原來快的多得多。參考:http://blog.csdn.net/bestcxx/article/details/49472035 ----------------------------------------------------代碼完 分割線--------------------------------------------------------------- 3、關于上面的limit,給dao層sql語句穿兩個參數,一個是開始取值的下標m,一個是每次取多少個n,其實就是limit 的兩個參數 limit m,n 如果最后一次分頁,未取數據剩余數目小于 n,這是沒關系的,不受影響。最后一次分頁剩45個數據沒取,我每次都取50,也不會報錯的啊。可我犯糊涂了,進行判斷是不是最后一次的分頁,是的話,就把傳的參數n改為經過計算得出的最后還沒取出的數目,比如 limit(m,45)。 4、還是關于代碼的編寫,盡量進行封裝復用。 比如上面xml解析代碼中的 materialInfo.setDETAIL_REQUIREMENT(elmMaterial.element("DETAIL_REQUIREMENT").getText()); 更改后把 elmMaterial.element("DETAIL_REQUIREMENT").getText() 放到一個方法中去, // 封裝從Node中獲取Element,并獲取Text private String getElementAsText(Element e, String nodeName ) { Element targetElement = e.element(nodeName); if ( null == targetElement ) return null; return targetElement.getText(); } 5、注解事務 改為 手動事務 這個要看具體場景靈活選擇。如果使用注解在方法A上,A中又調用方法B,B里面其中 x部分的代碼次啊是真正想要進行事務控制的地方,就這x處手動開啟 運用事務,而我之前只是簡單的把事務注解放在A上面完事,壓根就沒有考慮這個方面的問題。。。。 session.beginTransaction(); xxxxxxxxxxxxxxxxxxxxxxx; session.getTransaction().commit();
6、在新建dao層service層時,注意為以后的業務擴展考慮,不同表的增刪改查操作方法不要寫在一個dao里面,按照業務新建dao。其它層次也是同理。說到這想起了之前在北京的一位同事,當時給我講解傳參的場景,客戶需求總是變來變去,有時候參數傳遞自己不要寫的那么死,給自己留條路,便于自己修改,也利于項目代碼優化。 在此,真心向給與我過知道幫助的同事朋友說一聲謝謝。?( ′・?・` )比心 沒有什么特別大的高深內容,但都是我每一步菜的坑和小結心得吧,警醒自己吸取教訓,努力進步。
?
轉載于:https://www.cnblogs.com/dxxdsw/p/10673640.html
總結
以上是生活随笔 為你收集整理的解析xml数据存入bean映射到数据库的 需求解决过程 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。