但這樣的處理方法,有些缺點(diǎn),就是需要編碼對(duì)每個(gè)業(yè)務(wù)類(lèi)提供查詢(xún)方法,或在刪除邏輯中增加判斷邏輯。因此,每次引用關(guān)系變化,增加或減少時(shí)免不了要修改原來(lái)的邏輯,時(shí)間越長(zhǎng),系統(tǒng)的維護(hù)成本就越來(lái)越高了。因此,有必要對(duì)系統(tǒng)進(jìn)行重構(gòu),將這類(lèi)的處理邏輯進(jìn)行抽象,單獨(dú)封裝成一個(gè)服務(wù),當(dāng)引用關(guān)系有變更時(shí),不用再修改原有邏輯,通過(guò)配置就可以完成變更。
通用引用關(guān)系查詢(xún)服務(wù),主要就是通過(guò)db表或xml配置文件,對(duì)系統(tǒng)中每個(gè)基礎(chǔ)數(shù)據(jù)有引用的所有關(guān)系進(jìn)行定義,定義屬性主要是引用的表及字段名稱(chēng)。查詢(xún)時(shí),從配置文件中讀取指定類(lèi)別的引用關(guān)系,并逐一查詢(xún)這些表中的記錄,以確定數(shù)據(jù)是否被引用。這種處理方法的優(yōu)點(diǎn)為,易擴(kuò)展、可維護(hù)性強(qiáng),引用關(guān)系變更時(shí),僅通過(guò)維護(hù)配置文件,不必進(jìn)行編碼,就能實(shí)現(xiàn),這樣能大大的提高系統(tǒng)的穩(wěn)定性。
xml配置文件如下:
<rule bizName='product' desc="產(chǎn)品關(guān)聯(lián)項(xiàng)定義">
<item>
<refTable>sale_item</refTable>
<refField>product_id</refField>
<!-- 用于查詢(xún)條件的擴(kuò)展,允許為空 -->
<extCondition>CORP_ID = #corpId#</extCondition>
</item>
<item>
<refTable>sale_order_item</refTable>
<refField>product_id</refField>
<extCondition>CORP_ID = #corpId#</extCondition>
</item>
</rule>
<rule bizName='customer' desc="客戶(hù)關(guān)聯(lián)項(xiàng)定義">
<item>
<refTable>sale_order</refTable>
<refField>cust_id</refField>
<extCondition>CORP_ID = #corpId#</extCondition>
</item>
<item>
<refTable>sale_bill</refTable>
<refField>cust_id</refField>
<extCondition></extCondition>
</item>
... ...
</rule>
<item>
<refTable>sale_item</refTable>
<refField>product_id</refField>
<!-- 用于查詢(xún)條件的擴(kuò)展,允許為空 -->
<extCondition>CORP_ID = #corpId#</extCondition>
</item>
<item>
<refTable>sale_order_item</refTable>
<refField>product_id</refField>
<extCondition>CORP_ID = #corpId#</extCondition>
</item>
</rule>
<rule bizName='customer' desc="客戶(hù)關(guān)聯(lián)項(xiàng)定義">
<item>
<refTable>sale_order</refTable>
<refField>cust_id</refField>
<extCondition>CORP_ID = #corpId#</extCondition>
</item>
<item>
<refTable>sale_bill</refTable>
<refField>cust_id</refField>
<extCondition></extCondition>
</item>
... ...
</rule>
通用業(yè)務(wù)引用查詢(xún)類(lèi)代碼片段如下:
public class BizReferenceService implements IBizReferenceService {
private static Map<String,List<BizReferenceRule>> ruleMaps;
private static final String PATTERN = "#[\\w]+#";
private static final String CFG_FILE = "bizReferenceRule.xml";
... ...
/**
* 查詢(xún)指定業(yè)務(wù)數(shù)據(jù)是否被其他業(yè)務(wù)表關(guān)聯(lián)依賴(lài).
* @param bizName 關(guān)聯(lián)業(yè)務(wù)名稱(chēng)
* @param bizId 關(guān)聯(lián)業(yè)務(wù)ID.
* @param extParam 擴(kuò)展條件
* @return true 被關(guān)聯(lián)/false 未被關(guān)聯(lián).
*/
public boolean isBizReference(String bizName,String bizId,Map<String,Object>extParam) throws ServiceException {
Assert.notNull(bizName, "業(yè)務(wù)名稱(chēng)不能為空,bizName is NULL。");
Assert.notNull(bizId, "記錄ID不能為空,bizId is NULL。");
try {
//逐個(gè)檢查依賴(lài)項(xiàng)是否有數(shù)據(jù)關(guān)聯(lián).
List<BizReferenceRule> rules = getBizRelationRule(bizName);
for(BizReferenceRule rule : rules){
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("select count(*) from ").append(rule.getRelTable()).append(" where ")
.append(rule.getRelField()).append("='").append(bizId).append("' ");
String extConditon = rule.getExtCondition();
if(StringUtil.isNotBlank(extConditon)){
initTenantParam(extParam);
sqlBuilder.append(" and ").append(getExtParamSql(extConditon,extParam));
}
logger.debug(sqlBuilder);
int nCount = bizReferenceDao.getBizRelationCount(sqlBuilder.toString());
if (nCount != 0) return true;
}
return false;
}
catch(Exception ex){
logger.error("調(diào)用業(yè)務(wù)關(guān)聯(lián)服務(wù)錯(cuò)誤。"+bizName+",bizId:"+bizId+",extParam"+LogUtil.parserBean(extParam),ex);
throw new ServiceException("調(diào)用業(yè)務(wù)關(guān)聯(lián)服務(wù)錯(cuò)誤。");
}
}
/**
* 組裝擴(kuò)展查詢(xún)條件的sql
* @param condition
* @param extParam
* @return
* @throws Exception
*/
private String getExtParamSql(String condition,Map<String,Object>extParam) throws Exception {
List<String> paramList = parseDyncParam(condition);
for(String param : paramList){
String simpleParam = simpleName(param);
if(!extParam.containsKey(simpleParam)){
throw new ServiceException("動(dòng)態(tài)參數(shù)值未設(shè)置! param:"+param+",extParam:"+LogUtil.parserBean(extParam));
}
condition = condition.replaceAll(param, "'"+String.valueOf(extParam.get(simpleParam))+"'");
}
return condition;
}
/**
* 解析擴(kuò)展查詢(xún)條件中的動(dòng)態(tài)參數(shù)名.
* @param condition
* @return
* @throws Exception
*/
private List<String> parseDyncParam(String condition) throws Exception {
PatternCompiler compiler = new Perl5Compiler();
PatternMatcher matcher = new Perl5Matcher();
MatchResult result = null;
PatternMatcherInput input = null;
List<String> paramList = new ArrayList<String>();
input = new PatternMatcherInput(condition);
Pattern pattern = compiler.compile(PATTERN,Perl5Compiler.CASE_INSENSITIVE_MASK);
while (matcher.contains(input, pattern)){
result = matcher.getMatch();
input.setBeginOffset(result.length());
paramList.add(result.group(0));
}
return paramList;
}
/**
* 獲取業(yè)務(wù)關(guān)聯(lián)查詢(xún)規(guī)則.
*/
private List<BizReferenceRule> getBizRelationRule(String bizName){
Assert.notNull(bizName, "業(yè)務(wù)名稱(chēng)不能為空,bizName is NULL。");
//配置定義未加載到內(nèi)存時(shí),讀取配置文件
if(ruleMaps == null){
parseRuleConfig();
if(ruleMaps == null) return null;
}
return ruleMaps.get(bizName);
}
/**
* 讀取業(yè)務(wù)關(guān)聯(lián)規(guī)則配置文件
*/
@SuppressWarnings("unchecked")
private synchronized void parseRuleConfig(){
if(ruleMaps != null){
return;
}
//解析業(yè)務(wù)引用定義文件.

}
/**
* 讀取Xml文檔
* @return
*/
private Document getXmlDocument(){
InputStream is = null;
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
is = loader.getResourceAsStream(CFG_FILE);
SAXBuilder sb = new SAXBuilder();
return sb.build(new BufferedInputStream(is));
}
catch(Exception ex) {
logger.error("讀取配置文件錯(cuò)誤. file:"+CFG_FILE, ex);
return null;
}
finally {
try {
if(is != null){
is.close();
is = null;
}
}
catch(Exception ex) {
logger.error(ex);
}
}
}

}
private static Map<String,List<BizReferenceRule>> ruleMaps;
private static final String PATTERN = "#[\\w]+#";
private static final String CFG_FILE = "bizReferenceRule.xml";
... ...
/**
* 查詢(xún)指定業(yè)務(wù)數(shù)據(jù)是否被其他業(yè)務(wù)表關(guān)聯(lián)依賴(lài).
* @param bizName 關(guān)聯(lián)業(yè)務(wù)名稱(chēng)
* @param bizId 關(guān)聯(lián)業(yè)務(wù)ID.
* @param extParam 擴(kuò)展條件
* @return true 被關(guān)聯(lián)/false 未被關(guān)聯(lián).
*/
public boolean isBizReference(String bizName,String bizId,Map<String,Object>extParam) throws ServiceException {
Assert.notNull(bizName, "業(yè)務(wù)名稱(chēng)不能為空,bizName is NULL。");
Assert.notNull(bizId, "記錄ID不能為空,bizId is NULL。");
try {
//逐個(gè)檢查依賴(lài)項(xiàng)是否有數(shù)據(jù)關(guān)聯(lián).
List<BizReferenceRule> rules = getBizRelationRule(bizName);
for(BizReferenceRule rule : rules){
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("select count(*) from ").append(rule.getRelTable()).append(" where ")
.append(rule.getRelField()).append("='").append(bizId).append("' ");
String extConditon = rule.getExtCondition();
if(StringUtil.isNotBlank(extConditon)){
initTenantParam(extParam);
sqlBuilder.append(" and ").append(getExtParamSql(extConditon,extParam));
}
logger.debug(sqlBuilder);
int nCount = bizReferenceDao.getBizRelationCount(sqlBuilder.toString());
if (nCount != 0) return true;
}
return false;
}
catch(Exception ex){
logger.error("調(diào)用業(yè)務(wù)關(guān)聯(lián)服務(wù)錯(cuò)誤。"+bizName+",bizId:"+bizId+",extParam"+LogUtil.parserBean(extParam),ex);
throw new ServiceException("調(diào)用業(yè)務(wù)關(guān)聯(lián)服務(wù)錯(cuò)誤。");
}
}
/**
* 組裝擴(kuò)展查詢(xún)條件的sql
* @param condition
* @param extParam
* @return
* @throws Exception
*/
private String getExtParamSql(String condition,Map<String,Object>extParam) throws Exception {
List<String> paramList = parseDyncParam(condition);
for(String param : paramList){
String simpleParam = simpleName(param);
if(!extParam.containsKey(simpleParam)){
throw new ServiceException("動(dòng)態(tài)參數(shù)值未設(shè)置! param:"+param+",extParam:"+LogUtil.parserBean(extParam));
}
condition = condition.replaceAll(param, "'"+String.valueOf(extParam.get(simpleParam))+"'");
}
return condition;
}
/**
* 解析擴(kuò)展查詢(xún)條件中的動(dòng)態(tài)參數(shù)名.
* @param condition
* @return
* @throws Exception
*/
private List<String> parseDyncParam(String condition) throws Exception {
PatternCompiler compiler = new Perl5Compiler();
PatternMatcher matcher = new Perl5Matcher();
MatchResult result = null;
PatternMatcherInput input = null;
List<String> paramList = new ArrayList<String>();
input = new PatternMatcherInput(condition);
Pattern pattern = compiler.compile(PATTERN,Perl5Compiler.CASE_INSENSITIVE_MASK);
while (matcher.contains(input, pattern)){
result = matcher.getMatch();
input.setBeginOffset(result.length());
paramList.add(result.group(0));
}
return paramList;
}
/**
* 獲取業(yè)務(wù)關(guān)聯(lián)查詢(xún)規(guī)則.
*/
private List<BizReferenceRule> getBizRelationRule(String bizName){
Assert.notNull(bizName, "業(yè)務(wù)名稱(chēng)不能為空,bizName is NULL。");
//配置定義未加載到內(nèi)存時(shí),讀取配置文件
if(ruleMaps == null){
parseRuleConfig();
if(ruleMaps == null) return null;
}
return ruleMaps.get(bizName);
}
/**
* 讀取業(yè)務(wù)關(guān)聯(lián)規(guī)則配置文件
*/
@SuppressWarnings("unchecked")
private synchronized void parseRuleConfig(){
if(ruleMaps != null){
return;
}
//解析業(yè)務(wù)引用定義文件.


}
/**
* 讀取Xml文檔
* @return
*/
private Document getXmlDocument(){
InputStream is = null;
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
is = loader.getResourceAsStream(CFG_FILE);
SAXBuilder sb = new SAXBuilder();
return sb.build(new BufferedInputStream(is));
}
catch(Exception ex) {
logger.error("讀取配置文件錯(cuò)誤. file:"+CFG_FILE, ex);
return null;
}
finally {
try {
if(is != null){
is.close();
is = null;
}
}
catch(Exception ex) {
logger.error(ex);
}
}
}


}
其他的一些可選處理方法:
b. 在客戶(hù)表增加引用計(jì)數(shù)字段;
需額外維護(hù)引用計(jì)數(shù)字段,在引用的業(yè)務(wù)邏輯增加或刪除記錄時(shí),需對(duì)該字段的數(shù)值進(jìn)行更新。適用于需要直接查詢(xún)記錄被引用次數(shù)的場(chǎng)景,但在集群環(huán)境下,需注意并發(fā)問(wèn)題。