2007 年 3 月 30 日
在多個成員用一個系統進行集成測試時,每一個成員 PC 機上也要安裝一個相同的數據庫作為單元測試環境。本文介紹了保持各個數據庫一致性——我們稱這個過程為數據庫 schema 的同步——的幾種方式。
場景說明
我們在項目中經常遇到這樣一種情況:項目組所有成員共用一個系統集成測試環境(SIT)中的數據庫,每一個成員PC機上也安裝一個相同的數據庫作為單元測試環境。因為業務模型的變化,導致數據庫結構發生變化時,通常由數據庫管理員根據數據模型組的需求修改SIT的數據庫結構,並保持原有數據的延續性。此時,PC機上單元測試數據庫結構與SIT數據庫結構就可能不一致,通常有以下幾種方式來繼續保持它們的一致性(我們稱這個過程為數據庫SCHEMA的同步):
1、 由模型組或者數據庫管理員發布數據庫修改的命令清單,各成員按照清單逐步操作,實現數據庫SCHEMA的同步。這種方法要求每一次SIT數據庫SCHEMA的修改都必須被記錄在案,並通知到項目組每一個成員。如果某個成員遺漏了其中某一次修改,則他以後的修改都可能會有問題。當然,他也可以從其他成員的單元測試環境中備份、恢復最新版的數據庫。
2、 由數據庫管理員將SIT數據庫整庫備份,各成員在PC機上刪除原有數據庫,恢復該備份數據庫。這種方法看起來總能得到最新的SCHEMA和數據,但通常比較難於實施。因為SIT環境通常在UNIX環境下,而單元測試環境通常在Windows環境下,二者對於磁盤設備的管理方式相差甚大,通過數據庫備份工具得到的備份文件,在不同操作系統下恢復時,對於磁盤設備不同導致的修改相當麻煩。同時,SIT環境的數據量通常比較大,有的項目甚至於會定期從生產線上獲取最新的數據,而單元測試環境通常是在PC機上,所能提供的磁盤空間相對較小,多數情況下都不足以恢復全量的SIT數據庫。
3、 由模型組或者數據庫管理員發布數據庫修改的通知,各成員采用本文所描述的方法,根據SIT數據庫最新的SCHEMA,用PC機上的數據同步數據庫。這種方法采用本文提供的SHELL程序,可以比較快的完成,其中僅需要較小的人工干預。並且,這種方法也能保持單元測試環境中數據的延續性。
本文說明如何實現第三種方法,完成DB2數據庫的同步。
本文所使用的環境為:
本文使用如下約定:
DB2數據庫SCHEMA同步步驟
1、從SIT環境中取得最新的數據庫SCHEMA
2、從單元測試環境取得舊數據庫的SCHEMA
3、在單元測試環境,用db2move命令卸出舊數據庫的數據
4、比較兩個版本的異同
5、如果沒有任何表的SCHEMA發生了改變,則執行第7步
6、如果有表的SCHEMA發生了改變,則執行:
用新的表結構創建一個臨時表;
生成insert into transtmptbl (...) select ... from <oldtbl>的SQL;
將臨時表中的數據導出到文件中;
刪除臨時表;
用新的數據替換第3步導出的指定表的數據文件,然後執行第7步
7、取得新SCHEMA的表空間名稱
8、重建數據庫
9、有選擇的將新庫中獨有的表的數據導入到本地新數據庫中
10、檢查數據庫的完整性
以下,我們詳細說明這些步驟。
從SIT環境中取得最新的數據庫SCHEMA
在SIT環境中,執行以下命令:
db2look -d cusgadev -i db2admin -w db2admin -e -a -o cusnew.sql
該命令用於從SIT環境中抽取最新的DDL語句。其中各選項的涵義如下:
-d <dbname>:指定被抽取DDL的數據庫名稱。
-e:抽取數據庫的DDL語句,包括表、視圖、自動摘要表、別名、索引、觸發器、Sequences、User defined Distinct Types、Prima(最完善的虛擬主機管理系統)ry Key/RI/CHECK約束、用戶定義的結構化類型、用戶定義的函數、用戶定義的方法、用戶定義的轉換。
-i <userid>:連接遠程數據庫時,需要用這個選項指定登錄用戶名。
-w <passwd>:連接遠程數據庫時,需要用這個選項指定登錄用戶的密碼。
-a:輸出所有用戶創建的對象,而不僅僅是當前登錄用戶創建的對象。
-o <outfile>:將DDL輸出到指定的文件中。如果不指定該選項,則將輸出到標准輸出。
從單元測試環境取得舊數據庫的SCHEMA
在單元測試環境中,執行以下命令:
db2look -d cusgadev -e -a -o cusnew.sql
該命令用於從單元測試環境中抽取最新的DDL語句。各選項的涵義見上文。
在單元單元測試環境,卸出舊數據庫的數據
在單元測試環境中,執行以下命令:
db2move cusgadev export
該命令用於將單元測試環境中已有的數據輸出到當前目錄下。該命令為每一個表生成獨立的PC/IXF格式的數據文件,這些文件可以傳輸到任何其他的機器上並通過load或者import命令裝入到那台機器上的DB2數據庫中。
該命令同時生成一個名為"db2move.lst"的文件,這個文件是卸出表的清單,指明哪一個表對應到哪一個數據文件。該文件將用於之後的步驟,以便用最新的SCHEMA卸出原有表的數據。
分析新舊數據庫SCHEMA
以下,我們使用cygwin環境下的SHELL程序來分析新舊數據庫的SCHEMA。
格式化SCHEMA文件
為了比較新舊數據庫的SCHEMA,我們需要將兩個SCHEMA文件轉換成統一格式,包括:將所有大寫替換成小寫;刪除所有行首的空格;將多個空格合並成1個空格。其中,空格包括制表符(Tab)和空格字符(Space)。
使用以下命令組合格式化SCHEMA文件:
sed -e "s/^[ ]*//" \ -e "s/[ ][ ]*/ /g" \ ${OLDDBSCHEMA} | tr [:upper:] [:lower:] > ${OLDDBSCHEMATMP}
取得新舊數據庫的table清單
定義如下函數,從數據庫的SCHEMA文件中取得其中的table清單。這個函數適用於由db2look生成的、DB2數據庫的DDL文件。
# 根據數據庫的SCHEMA文件,取得其中的table清單# $1. 數據庫的SCHMEA文件# $2. 輸出的table清單文件gettbllist(){ grep -i "^[ ]*create[ ][ ]*table" $1 > $2.tmp sed -e "s/^[ ]*CREATE[ ][ ]*TABLE//" \ -e "s/(.*$//" \ $2.tmp | sort > $2 rm $2.tmp}
找出僅在舊庫或者僅在新庫獨有的表
定義如下函數,取出僅在指定數據庫SCHEMA中存在的table的清單。這個函數適用於由db2look生成的、DB2數據庫的DDL文件。
# 取出僅在第一個參數指定的數據庫SCHEMA中存在的表的名稱清單# $1: 待分析的數據庫SCHEMA文件# $2: 用作參照的數據庫SCHEMA文件# $3: 屏幕輸出的提示信息gettableonlyinone(){if [ $# -eq 3 ]thenecho $3fiTABLEINBOTH=""for tblname in `awk -F"." '{printf("%s\n", $2);}' $1 | awk -F"\""'{printf("%s ", $2)}' | sort`dogrep -i "\"$tblname\"" $2 1>/dev/null 2>&1if [ ! $? -eq 0 ]thenif [ $# -eq 3 ]thenecho $tblnamefIElseTABLEINBOTH="${TABLEINBOTH} ${tblname}"fidone}
對於僅在舊數據庫中存在的表,需要從db2move.lst中刪除該表對應的行。
對於僅在新數據庫中存在的表,需要根據需要有選擇的從新數據庫中卸出數據並裝入到新數據庫中。
找出結構不同的表,並生成卸出數據的SQL腳本
通過以下步驟,找到在新舊數據庫中都存在,並且結構發生了變化的table,並生成重新卸出數據的SQL語句。
# 根據數據庫的SCHEMA文件,取得指定table的SCHEMA# $1: table名稱# $2: 數據庫的SCHEMA文件,需要先做格式化# $3: 輸出的表的SCHEMA文件gettableschema(){#echo "table name: " $1#echo "dbschema name: " $2#echo "outfile name: " $3awk -v_tblname=$1 'BEGIN{isThisTable = 0;}{if($1 == "create" && $2 == "table" && match($0, "\""_tblname"\"") > 0){isThisTable = 1;printf("create table %s (\n", _tblname); #### )}else if(isThisTable == 1){if(match($0, ";") > 0){isThisTable = 0;if($1 == "in")printf("; \n", $0);elseprintf("%s\n", $0);}else if(match($0, "timestamp not null with default ,") > 0){printf("%s timestamp not null with default current timestamp , \n", $1);}else{printf("%s\n", $0);}}}' $2 > $3}# 生成卸出指定表的數據的SQL腳本# $1. 舊數據庫的SCHEMA文件# $2. 新數據庫的SCHEMA文件# $3. 輸出的卸出指定表的數據的SQL腳本文件名# $4. 指定的表名# $5. db2move卸出文件時的清單文件genunloadsql(){TMPSQLFILE=.tmpsql.sql.sqlTMPNEWTBLFLD=.tmpfld.fld.fldrm -f ${TMPSQLFILE}# 1. 用新的表結構創建一個臨時表awk '{if(NR > 1){gsub("\"", "", $1); # remove character "printf("%s\n", $0);}else{printf("create table sihitranstmptbl (\n"); # )}}' $2 > ${TMPSQLFILE}# 2. 生成insert into transtmptbl (...) select ... from <oldtbl>的SQL# 2.1. 生成新舊兩個表的字段列表awk '{if(NR > 1 && $1 != ";"){gsub("\"", "", $1); # remove character "printf("%s\n", $1);}}' $2 > ${TMPNEWTBLFLD}isFirstFeild=1Select=""Into=""for fldname in `cat ${TMPNEWTBLFLD}`dogrep "\"${fldname}\"" $1 1>/dev/null 2>&1if [ $? -eq 0 ]then# 2.2. 取出在新舊兩表中都有的字段名,加入到select子句和into子句中if [ ${isFirstFeild} -eq 0 ]thenSelect="${Select},"Into="${Into},"fiSelect="${Select} ${fldname}"Into="${Into} ${fldname}"isFirstFeild=0else# 2.3. 取出僅在新表中出現的字段,如果該字段不允許null,# 且沒有設置default值,則按以下原則取默認值放到select子句中,# 並將字段名放到into子句中grep "\"${fldname}\"" $2 | grep "not null" 1>/dev/null 2>&1if [ $? -eq 0 ]thengrep "\"${fldname}\"" $2 | grep "with default" 1>/dev/null 2>&1if [ ! $? -eq 0 ]thenif [ ${isFirstFeild} -eq 0 ]thenSelect="${Select},"Into="${Into},"fi# 計算默認值# 對於新增的字符型字段,默認值為'';# 對於新增的數值型字段,默認值為0;# 對於新增的TIMESTAMP字段,默認值為'';# 對於新增的SERIAL開字段,默認值為0;# 對於新增的DATE字段,默認值為;# 對於新增的DATETIME字段,默認值為;Const=`grep "\"${fldname}\"" $2 | awk '{if(match($2, "int") > 0) # integer, smallint, bigintprintf("0");else if(match($2, "numeric") > 0)printf("0.0");else if(match($2, "decimal") > 0)printf("0.0");else if(match($2, "double") > 0)printf("0.0");else if(match($2, "float") > 0)printf("0.0");else if(match($2, "real") > 0)printf("0.0");else if(match($2, "char") > 0) # char, varcharprintf("\" \"");else if($2 == "timestamp")printf("current timestamp");else if($2 == "date")printf("current date");else if($2 == "time")printf("current time");elseprintf("\" \"");}'`Select="${Select} ${Const}"Into="${Into} ${fldname}"isFirstFeild=0fififidonetablefullname=`grep -i "\"$4\"" $5 | awk -F"!" '{printf("%s", $2);}'`echo "insert into sihitranstmptbl (${Into}) select ${Select} from${tablefullname} ;" >> ${TMPSQLFILE}# 3. 將臨時表中的數據導出到文件中Unloadfile=`grep -i "\"$4\"" $5 | awk -F"!" '{printf("%s", $3);}'`echo "export to ${Unloadfile} of ixf select * from sihitranstmptbl ;">> ${TMPSQLFILE}# 4. 刪除臨時表echo "drop table sihitranstmptbl ;" >> ${TMPSQLFILE}cat ${TMPSQLFILE} >> $3rm -f ${TMPSQLFILE}rm -f ${TMPNEWTBLFLD}}echo "table in both database:"TABLEINBOTH="`echo ${TABLEINBOTH} | tr [:upper:] [:lower:]`"echo > ${UNLOADSQLFILE}for tblname in ${TABLEINBOTH}dogettableschema ${tblname} ${OLDDBSCHEMATMP} ${OLDTBLSCHEMA}gettableschema ${tblname} ${NEWDBSCHEMATMP} ${NEWTBLSCHEMA}diff ${OLDTBLSCHEMA} ${NEWTBLSCHEMA} 1>/dev/null 2>&1if [ ! $? -eq 0 ]then# 如果有不同,則生成卸出數據的SQL腳本echo "different table:" ${tblname}genunloadsql ${OLDTBLSCHEMA} ${NEWTBLSCHEMA} ${UNLOADSQLFILE} ${tblname} ${DB2MOVELISTFILE}elseecho "same table:" ${tblname}fidone
其中,TABLEINBOTH是由上一步(找出僅在舊庫或者新庫中獨有的表)的副產品。
注意,上述程序中,並沒有處理新舊table中均有並且字段類型不同的字段,這種情況可以在卸出數據時按需要手工編輯生成的SQL腳本。
生成的SQL腳本名稱叫"unload.sql"。在PC機上的DB2命令窗口中執行:
db2 -z result.txt -tvf unload.sql
其中,"-z"選項將執行結果同時輸出到屏幕及文件result.txt中。執行結束時,需要查看result.txt,如果其中有錯誤提示,請按需要修改unload.sql。修改之後,再重新執行上述命令,直到所有SQL命令均執行無錯為止。
取得新SCHEMA的表空間名稱
通過以下步驟,找出新數據庫使用的表空間的名稱,並給出創建數據庫緩沖池及表空間的SQL的建議。
# 8. 根據新數據庫SCHEMA文件取得tablespace的名稱清單# 並給出創建緩沖池及tablespace的SQL建議echo "創建緩沖池的命令: "echo "create bufferpool BF81 size 10000 pagesize 8 K"echo "創建表空間的命令: "echo "--------------------------------------------------------------"for tblspace in ` grep -i "^[ ]*in[ ]" ${NEWDBSCHEMA} | grep ";" | awk '{printf("%s\n", $2);if(NF > 3){if($3 == "INDEX" && $4 == "IN"){printf("%s\n", $5);}}}' | sort | uniq `doecho "CREATE TABLESPACE ${tblspace} PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81"doneecho "--------------------------------------------------------------"
執行結果類似於:
創建緩沖池的命令:
create bufferpool BF81 size 10000 pagesize 8 K
創建表空間的命令:
--------------------------------------------------------------CREATE TABLESPACE "DATATBS00_8K" PAGESIZE 8K MANAGED BY DATABASE USING (FILE'<filename>' 50000) bufferpool bf81CREATE TABLESPACE "DATATBS01_8K" PAGESIZE 8K MANAGED BY DATABASE USING (FILE'<filename>' 50000) bufferpool bf81CREATE TABLESPACE "DATATBS02_8K" PAGESIZE 8K MANAGED BY DATABASE USING (FILE'<filename>' 50000) bufferpool bf81CREATE TABLESPACE "IDXTBS00_8K" PAGESIZE 8K MANAGED BY DATABASE USING (FILE'<filename>' 50000) bufferpool bf81CREATE TABLESPACE "IDXTBS02_8K" PAGESIZE 8K MANAGED BY DATABASE USING (FILE'<filename>' 50000) bufferpool bf81CREATE TABLESPACE "STATICTBS00_8K" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81CREATE TABLESPACE "USERSPACE1" PAGESIZE 8K MANAGED BY DATABASE USING (FILE'<filename>' 50000) bufferpool bf81--------------------------------------------------------------
在實際創建表空間時,需要將其中的"<filename>"替換成Windows系統下的一個文件名稱,每一個表空間使用不同的文件名。也可以根據對數據庫中數據分布的估計,調整表空間的大小。
重建數據庫
按以下步驟重建數據庫:
1、編輯db2move.lst,刪除在新庫中已不使用的表。
根據上述步驟中得到的僅在舊數據庫中存在的表,編輯db2move.lst,刪除該表所在的行。
2、刪除舊數據庫。
執行以下命令,刪除舊數據庫:
db2 drop database cusgadev
3、創建數據庫
執行以下命令,創建新數據庫:
db2 create database cusgadev using codeset iso8859-1 territory cn
其中,"iso8859-1"表示數據庫使用的字符集,"cn"表示數據庫使用的地域。
4、連接數據庫
執行以下命令,連接新數據庫:
db2 connect to cusgadev
5、修改物理日志參數
執行以下命令,修改數據庫的日志參數:
db2 update database configuration using logfilsiz 25000 deferred
創建數據時,默認的日志空間比較小,對於大事務的處理有影響,所以,需要根據應用的需要及PC機的配置適當調整日志空間的值。
可以使用"GET DATABASE CONFIGURATION"命令查看數據庫的當前配置。
6、創建緩沖池
執行以下命令,為數據庫創建緩沖池:
db2 create bufferpool BF81 size 10000 pagesize 8 K
7、創建表空間
執行以下命令,為數據庫創建表空間:
db2 CREATE TABLESPACE "DATATBS00_8K" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81db2 CREATE TABLESPACE "DATATBS01_8K" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81db2 CREATE TABLESPACE "DATATBS02_8K" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81db2 CREATE TABLESPACE "IDXTBS00_8K" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81db2 CREATE TABLESPACE "IDXTBS02_8K" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81db2 CREATE TABLESPACE "STATICTBS00_8K" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81db2 CREATE TABLESPACE "USERSPACE1" PAGESIZE 8K MANAGED BY DATABASE USING(FILE '<filename>' 50000) bufferpool bf81
其中,tablespace的名稱是從新數據庫的SCHEMA文件中提取出來的。本例中,它們都使用同一個緩沖池。"<filename>"需要按照PC機硬盤空余空間的情況,修改為實際的全路徑文件名稱。
8、用新的SCHEMA創建數據庫
執行以下命令,按照新數據庫的SCHEMA創建其中的各種對象,如表、索引等:
db2 -tvf cusnew.sql
9、裝入數據
執行以下命令,將舊數據庫的數據裝入到剛創建的新數據庫中:
db2move cusgadev load
該命令使用的修改後的db2move.lst作為裝入表的清單。
有選擇的將新庫中獨有的表的數據導入到本地新數據庫中對於僅在新數據庫中存在的表,可以根據需要有選擇的將其數據從SIT環境導入到單元測試環境。本文中,不考慮新表對於其他表的外鍵關聯。
假設某個僅在新數據庫中存在的表的名稱為"newtable",在SIT環境執行以下命令卸出數據:
db2 unload to newtable.ixf of ixf select * from newtable
將文件"newtable.ixf"傳輸到單元測試環境所在的PC機上,執行以下命令:
db2 load from newtable.ixf of ixf insert into newtable
如果這個新表對於其他表有外鍵關聯,還需要導入其他相關表的數據。
檢查數據庫的完整性
檢查完整性的SHELL程序
以下是"CheckIntegrity.sh"的源代碼。該程序用於在數據load結束以後,檢查數據庫的完整性。
#!/usr/bin/sh# 在數據load結束後,檢查數據庫的完整性# Copyright: SI HITECH 2006# 作 者: 鄭靖華# 創建日期: 2006 年 8 月 2 日# 指定數據庫名稱if [ $# -lt 1 ]thenDBNAME=ccdbelseDBNAME=$1fi# 檢查db2move的lst文件if [ ! -r db2move.lst ]thenecho "當前目錄下沒有db2move的清單文件 [ db2move.lst ],請確保目錄正確並再次執行本命令!"exit 1fi# 生成執行完整性檢查的SHELL程序awk -F"!" -v_dbname=${DBNAME} 'BEGIN{printf("connect to %s;\n", _dbname);}{printf("set integrity for %s immediate checked;\n", $2);}' db2move.lst > $DBNAME.integrity.sql# 執行檢查程序db2 -tvf $DBNAME.integrity.sqlecho "完整性檢查完畢,請仔細查看檢查結果!"exit 0
所謂外鍵關聯鏈,是指A表依賴於B表,B表依賴於C表。只有當C表的外鍵關聯檢查成功以後,才能檢查B表;同樣的,只有當B表的外鍵關聯檢查成功以後,才能檢查A表。即為了檢查A表,可能需要多次執行上述SHELL程序。
所謂外鍵關聯環,是指A表直接或者間接依賴於自身(A表)。比如,A表的a1字段依賴於A表的a2字段,同時,A表的a2字段也依賴於A表的a3字段(其中,a1、a2、a3可以相同,也可以不同),這是一個直接關聯的環。再如,A表的a1字段依賴於B表的b1字段,同時,B表的b2字段依賴於A表的a2字段(其中,a1和a2、b1和b2可以相同,也可以不同),這是一個間接關聯的環。如果存在這樣的環,我們必須通過刪除其中的某一個或幾個依賴來打破這個環,在完整性檢查結束以後,再重建那些被臨時刪除的依賴。
驗證完整性的SHELL程序
我們通過查詢所有表的記錄數,來驗證所有表的完整性是否都已經檢查成功。以下是"CntDb.sh"的源碼。
#!/usr/bin/sh# 在數據load結束後,取得數據庫每一個表的記錄數# Copyright: SI HITECH 2006# 作 者: 鄭靖華# 創建日期: 2006 年 8 月 3 日# 指定數據庫名稱if [ $# -lt 1 ]thenDBNAME=ccdbelseDBNAME=$1fi# 檢查db2move的lst文件if [ ! -r db2move.lst ]thenecho "當前目錄下沒有db2move的清單文件 [ db2move.lst],請確保目錄正確並再次執行本命令!"exit 1fi# 生成執行完整性檢查的SHELL程序awk -F"!" -v_dbname=${DBNAME} 'BEGIN{printf("connect to %s;\n", _dbname);}{tbname = $2;gsub("\"", "", tbname);gsub(" ", "", tbname);printf("select count(*) from %s;\n", tbname);}' db2move.lst > $DBNAME.cnt.sql# 執行檢查程序db2 -tvf $DBNAME.cnt.sqlecho "完整性檢查完畢,請仔細查看檢查結果!"exit 0
如果某個表未完成完整性檢查,則在執行上述SHELL程序時,會出現下述錯誤提示:
select count(*) from sampletable1-----------SQL0668N 由於表 "SAMPLETABLE" 上的原因代碼 "1",所以不允許操作。SQLSTATE=57016
這個錯誤代碼及原因代碼,表示該表正處於"Check Pending"狀態,需要用"IMMEDIATE CHECKED"選項執行"SET INTEGRITY"命令,即立即檢查其完整性。
打破外鍵關聯的環,檢查完整性
通過多次執行上述兩個SHELL程序,可以判斷出數據庫SCHEMA中是否存在外鍵關聯環。通過分析數據庫SCHEMA文件,找出這個環,並找到這個環上的一個依賴關系,執行以下步驟:
1、找到外鍵關聯環上的一個依賴關系
2、刪除這個外鍵
db2 ALTER TABLE a1 DROP FOREIGN KEY a1_FK02
3、檢查完整性並驗證完整性
執行"CheckIntegrity.sh"、"CntDb.sh",如果完整性檢查成功,則執行第4步;如果多次執行這兩個SHELL後,完整性均沒有檢查成功,說明可能還有其他的外鍵關聯環,需要再次執行第1步。
4、重建這個外鍵關聯
從新數據庫SCHEMA文件中提取出剛才被臨時刪除的外鍵關聯的語句,在命令行執行這些語句。
參考資料
關於作者
鄭靖華是北京宇信易誠科技有限公司的項目經理,致力於金融行業應用的開發和管理。