Cache死循环检测和申明式事务

死循环检测问题
Cache作为后关系型数据库,使用其提供M语言操作Global数据结构。可以达到极快的查询速度。M语言作为比C还低级的语言加之难用的代码编辑器和弱类型,非常容易出现死循环,就算你是多年老手也一个不小心就踩坑。

据我总结,崩溃问题不是个人完全好避免的,从罚款和培训的层面难以彻底解决。可以基于AOP切面设计一套死循环避免体系,从架构上解决死循环难以发现引起的数据库崩溃问题。

处理分为以下几个要点:
1.在程序调用数据库M的入口处记住当前进程号和时间以及参数
2.在程序调用数据库M的返回出口前删除上面记录的当前进程的信息
3.在执行要调用的具体类方法前遍历没被删除的记录的进程信息,检测时间超过10分钟的进程结束(用户界面哪有执行10分钟还没响应的,把结束的进程记录进入杀进程信息中)

代码实现
要点部分在方法DealOldJob里面部分
//防止M死循环部分******************************************
//防止M死循环部分******************************************

/// LIS.WS.DHCLISService
/// 服务接口
Class LIS.WS.DHCLISService Extends %SOAP.WebService [ ProcedureBlock ]
{

/// Name of the WebService.
Parameter SERVICENAME = "LISService";

/// TODO: change this to actual SOAP namespace.
/// SOAP Namespace for the WebService
Parameter NAMESPACE = "http://tempuri.org";

// Parameter LOCATION = "http://111.205.100.65/csp/lis";

/// Parameter LOCATION = "http://172.19.19.43/csp/lis";
/// TODO: add arguments and implementation.
/// GetData %GlobalCharacterStream
/// w ##Class(LIS.WS.DHCLISService).GetData("LIS.WS.BLL.DHCQCResultAppraise","QryAppraiseBaseMTHD","<Parameter><P0>108</P0><P1>255</P1><P2>1</P2><P3>2020-04-30</P3></Parameter>","113^1^0^13^1")
/// w ##Class(LIS.WS.DHCLISService).GetData("LIS.WS.BLL.DHCRPVisitNumberReportResult","GetHisResultJSONStream","<Parameter><P0>1</P0><P2>10</P2></Parameter>","113^7^0^12^1")
ClassMethod GetData(ClassName As %String, FuncName As %String, Param As %String(MAXLEN=99999999), Session As %String(MAXLEN=1000)) As %GlobalCharacterStream [ WebMethod ]
{
	s ClassName=$g(ClassName),FuncName=$g(FuncName),Param=$g(Param),Session=$g(Session)
	s ClassName=..DealNotSeeChar(ClassName)
	s FuncName=..DealNotSeeChar(FuncName)
	s Session=..DealNotSeeChar(Session)
	s FunModul=$p(FuncName,"^",2)
	s FuncName=$p(FuncName,"^",1)
	s DBCurDateTime=$h
	Set $ZTrap = "ErrorHandle"
	//s ^TMP("GetData",ClassName)=$LB(ClassName,FuncName,Param,Session)
	s (P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,P14)=""
	s OutPutType=$p(Session,"^",7)   //JSON:Query直接输出JSON串
	s SortName=$zcvt($p(Session,"^",8),"U")    ///排序列名
    s SortType=$zcvt($p(Session,"^",9),"U")   ///排序方式  ASC  DESC
    s IsStr=$p(Session,"^",10)   ///完全用字符串排序 1
    s RequestUniqueKey=$p(Session,"^",11)   ///通过GetRequestUniqueKey得到的查询唯一标识,只变分页条件就取缓存的查询数据,提高分页速度
	
	s objStream=##class(%GlobalCharacterStream).%New()
    Set reader = ##class(%XML.Reader).%New()
    // Begin processing of the String
    Do reader.OpenString(Param)
    // Associate a class name with the XML element name
    Do reader.Correlate("Parameter","LIS.WS.Model.InputParameter")
    While (reader.Next(.obj,.sc)) {
	    If ($SYSTEM.Status.IsOK(sc)) // check if this succeeded without errors
	    {
		    s P0=$tr(obj.P0,$c(0),"")
		    s P1=$tr(obj.P1,$c(0),"")
		    s P2=$tr(obj.P2,$c(0),"")
		    s P3=$tr(obj.P3,$c(0),"")
		    s P4=$tr(obj.P4,$c(0),"")
		    s P5=$tr(obj.P5,$c(0),"")
		    s P6=$tr(obj.P6,$c(0),"")
		    s P7=$tr(obj.P7,$c(0),"")
		    s P8=$tr(obj.P8,$c(0),"")
		    s P9=$tr(obj.P9,$c(0),"")
		    s P10=$tr(obj.P10,$c(0),"")
		    s P11=$tr(obj.P11,$c(0),"")
		    s P12=$tr(obj.P12,$c(0),"")
		    s P13=$tr(obj.P13,$c(0),"")
		    s P14=$tr(obj.P14,$c(0),"")
	    }
        Else // if there was an error, break out of the While loop
        {
            Quit
        }
        //If $system.Status.IsError(sc) do $system.OBJ.DisplayError(sc) 
    } 
    If $system.Status.IsError(sc) q ##Class(LIS.Util.Response).GetReturn(ClassName,"-1","参数错误")
    
    //防止M死循环部分******************************************
    //记录当前进程开始时间,杀掉超时进程
    s DBCurDateTime=##Class(LIS.WS.DHCLISServiceBase).DealOldJob(ClassName,FuncName,Param,Session)
	//防止M死循环部分******************************************
	
    s PageSize=P12
	s PageIndex=P13
 	//查询
	s RowCount=0
	//0数据集,1JSON串
 	s ResType=0  
 	//返回结果
 	s objStream=##class(%GlobalCharacterStream).%New()
 	d objStream.Write("<Response>")
 	//错误信息
 	s Err=""
 	//申明式事务
 	if ($d(^LISDeclarativeTrans(ClassName,FuncName)))
 	{
	 	s retJson=##Class(LIS.WS.DHCLISServiceBase).DeclarativeTrans(ClassName,FuncName,P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,Session,.RowCount,"",Err)
	 	s retJson=##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(retJson)
		s ResType=1
 	}
 	elseif(($e(FuncName,$l(FuncName)-3,$l(FuncName))="MTHD")||(FunModul="MTHD"))
 	{
		s retJson=$CLASSMETHOD(ClassName,FuncName,P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,Session,.RowCount)
		s retJson=##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(retJson)
		s ResType=1
	} 
	elseif((($e(FuncName,$l(FuncName)-9,$l(FuncName))="JSONStream")||(FunModul="JSONStream")))
	{
		//直接输出JSON传
	    s retJsonStream=$CLASSMETHOD(ClassName,FuncName,P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,Session,.RowCount)
	    d objStream.Write("<"_FuncName_"Result>")
	    i $ISOBJECT(retJsonStream) d objStream.CopyFrom(retJsonStream)
	    e  d objStream.Write(##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(retJsonStream))
	    d objStream.Write("</"_FuncName_"Result>")
	    s ResType=1 
	    i Session=$c(0) s Session=""
	    d objStream.Write("<RetVal>0</RetVal><Error></Error><Node>"_FuncName_"</Node><RowCount>"_RowCount_"</RowCount><ResType>"_ResType_"</ResType>"_"<RetSession>"_Session_"</RetSession>") //QryRequest
	    d objStream.Write("</Response>")
	    //有事务没完成记录日志
	    i $TLEVEL>0 d
	    .s ^LISCoreKillJobInfo(DBCurDateTime,"开放事务")=$g(^TMPLISCore("DBCARE",DBCurDateTime,$j))
	    //删除记录
	    k ^TMPLISCore("DBCARE",DBCurDateTime,$j)
	    i $TLEVEL>0 TROLLBACK  q ##Class(LIS.Util.Response).GetReturn("","-1",ClassName_"的方法"_FuncName_"返回前事务层级不为0,存在开放性事务!")
	 	q objStream
	}
	else 
	{
	 	Set rset=##class(%XML.DataSet).%New()
	 	// "LIS.WS.BLL.DHCRequest"
	    Set rset.ClassName = ClassName 
	    //"QryRequest"
	    Set rset.QueryName = FuncName 
		s rset.NeedSchema=1
		s rset.DiffGram=0
		s PageSize=""
		s PageIndex=""
		//分页排序统一公共处理
		i OutPutType="JSON" d
		.//有排序列或者分页页码不是第一页就采用分页缓存
		.i ($l(SortName)||($l(P13)&&P13>1)) s PageSize=P12,PageIndex=P13,(P12,P13)="" 
		.e  s RequestUniqueKey=""
		//查询参数串
		s QryParaStr=""
		//存命中的缓存索引
		s CurCacheIndex=""
		//有请求唯一键就尝试缓存
		i $l(RequestUniqueKey) d
		.//缓存参数
		.s QryParaStr=P0_"$$"_P1_"$$"_P2_"$$"_P3_"$$"_P4_"$$"_P5_"$$"_P6_"$$"_P7_"$$"_P8_"$$"_P9_"$$"_P10_"$$"_P11
		.//是否找到唯一键相同的缓存
		.i $d(^TMPLISSortJsonCacheI(RequestUniqueKey)) d
		..s CacheIndex=$g(^TMPLISSortJsonCacheI(RequestUniqueKey))
		..//查询条件一样,命中缓存
		..i $g(^TMPLISSortJsonCache(CacheIndex,RequestUniqueKey,0))=QryParaStr d
		...//清空rset,后面不走读取数据
		...s rset=""
		...//缓存索引
		...s CurCacheIndex=$g(^TMPLISSortJsonCacheI(RequestUniqueKey))
		...//存的总数
		...s RowCount=$g(^TMPLISSortJsonCache(CacheIndex,RequestUniqueKey,1))
		//没有缓存就执行方法查询
		i '$l(CurCacheIndex) Set sc=rset.Execute(P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,.Session,.RowCount)
		//Query直接输出JSON串
		if (OutPutType="JSON")
		{
			//0:使用返回的其他信息,1:只使用返回的结果串tStream,2:使用外层的RowCount
			s ResType=2
			s RowCountNoNeed=0
			d objStream.Write("<"_FuncName_"Result>")
			s tStream=##Class(LIS.WS.BLL.DHCDataJSON).DataSetToSortJSONStream(rset,SortName,SortType,PageSize,PageIndex,.RowCountNoNeed,IsStr,objStream,RequestUniqueKey,CurCacheIndex,QryParaStr,.Err,"1")
     		d objStream.Write("</"_FuncName_"Result>")
    		d objStream.Write("<RetVal>0</RetVal><Error></Error><Node>"_FuncName_"</Node><RowCount>"_RowCount_"</RowCount><ResType>"_ResType_"</ResType>"_"<RetSession>"_Session_"</RetSession>") //QryRequest
    		d objStream.Write("</Response>")
    		//有事务没完成记录日志
	    	i $TLEVEL>0 d
	    	.s ^LISCoreKillJobInfo(DBCurDateTime,"开放事务")=$g(^TMPLISCore("DBCARE",DBCurDateTime,$j))
    		//删除记录
	    	k ^TMPLISCore("DBCARE",DBCurDateTime,$j)
	    	//有事务没完成
	    	i $TLEVEL>0 TROLLBACK  q ##Class(LIS.Util.Response).GetReturn("","-1",ClassName_"的方法"_FuncName_"返回前事务层级不为0,存在开放性事务!")
 			q objStream
		}
		
		//方法名称后4位为JSON的为返回JSON串,其他返回数据集 20140729
		if ($e(FuncName,$l(FuncName)-3,$l(FuncName))="JSON")
		{   
			s retJson=##Class(LIS.WS.BLL.DHCDataJSON).DataSetToJSON(rset,Err)
			s retJson=##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(retJson)
			s ResType=1
		}
		else
		{   
			s ResType=0
			s RowCountNoNeed=0
			if $l(SortName) {
				//DataSet转为XML数据流
				s Xml=##class(LIS.Util.DataSetXML).DataSetToSortStream(rset , SortName, SortType, PageSize, PageIndex, .RowCountNoNeed,IsStr,objStream,.Err)
			} 
			else {
				//d rset.XMLExportToStream(.Xml)
				s Xml=##class(LIS.Util.DataSetXML).DataSetToSortStream(rset , SortName, SortType, -1, -1, .RowCountNoNeed,IsStr,objStream,.Err)
			}
			

		}
		//d rset.SetArgs(P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,P14)
		//d rset.XMLExportToString(.a)
		d rset.Close()
	}
  	
    if (ResType=1)
    {
     	d objStream.Write("<"_FuncName_"Result>"_retJson_"</"_FuncName_"Result>")
    }
    i Session=$c(0) s Session=""
    d objStream.Write("<RetVal>0</RetVal><Error>"_##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(Err)_"</Error><Node>"_FuncName_"</Node><RowCount>"_RowCount_"</RowCount><ResType>"_ResType_"</ResType>"_"<RetSession>"_Session_"</RetSession>") //QryRequest
    d objStream.Write("</Response>")
    //有事务没完成记录日志
	i $TLEVEL>0 d
	.s ^LISCoreKillJobInfo(DBCurDateTime,"开放事务")=$g(^TMPLISCore("DBCARE",DBCurDateTime,$j))
    //删除记录
	k ^TMPLISCore("DBCARE",DBCurDateTime,$j)
	//有事务没完成
	i $TLEVEL>0 TROLLBACK  q ##Class(LIS.Util.Response).GetReturn("","-1",ClassName_"的方法"_FuncName_"返回前事务层级不为0,存在开放性事务!")
 	q objStream
 	
ErrorHandle
	//有事务没完成记录日志
	i $TLEVEL>0 d
	.s ^LISCoreKillJobInfo(DBCurDateTime,"开放事务")=$g(^TMPLISCore("DBCARE",DBCurDateTime,$j))
	//删除记录
	k ^TMPLISCore("DBCARE",DBCurDateTime,$j)
    s err=$tr("查询异常.错误"_$tr($ZERROR,"^","--")_".错误代码:"_$ECODE,"<>")
    i $TLEVEL>0 TROLLBACK  s err=err_ClassName_"的方法"_FuncName_"返回前事务层级不为0,存在开放性事务!"
    s objStream=##Class(LIS.Util.Response).GetReturn("","-1",err)
    Quit objStream
}

/// zlz 去除不可见字符
ClassMethod DealNotSeeChar(str As %String) As %String
{
    s str=$tr(str,$c(127))
    f z=0:1:31 s str=$tr(str,$c(z))
    q str
}



/// w ##Class(LIS.WS.DHCLISService).GetSQLData("$StoredProcedure^LISSP.DHCRPVisitNumber_GetVisitNumberCheckedMSG","$ZLPS$<Data><P6></P6><P9></P9><P8></P8><P0></P0><P12></P12><P13></P13><P10></P10><P11></P11><P4></P4><P14></P14><P7></P7><P1>2020-02-07</P1><P3>9</P3><P2>2</P2><P5></P5></Data>$ZLPS$113^3^0^12^1$ZLPS$",$c(0))
/// w ##Class(LIS.WS.DHCLISService).GetSQLData("INSERT INTO dbo.BTMI_MachineParameter(Active,DelimiterForSen,Serialnumber,DelimiterForAnt,RegisterDeviceDR,CommDirection,Sequence,LicenseKey,BaudRate,ViewQcMap,OpMessage,CName,RoomDR,Parity,PortNo,DelimiterForResults,WorkGroupMachineDR,IsChgTranEpis,DeviceCode,IFProgram,DelimiterForTests,IPAddress,JobID,StopBits,IsStart,StartWebService,ComPort,LName,HospitalDR,LinkWGMachines,Code,IsSendPanic,DataBits) VALUES('1','','','','','UI','1','','9600','','','微生物','','E','','\','16','0','1','',',','','','2','','','0','微生物','1','','MIC','0','7')",$c(0),$c(0))
/// w ##Class(LIS.WS.DHCLISService).GetSQLData("SELECT Remark,RowID,UpdateUser,UpdateDate FROM dbo.SYS_UpdateLog WHERE RowID = ?","-1^-1","3")
/// w ##Class(LIS.WS.DHCLISService).GetSQLData("update dbo.BT_Hospital set CName='航空' where RowID=17")
/// w ##Class(LIS.WS.DHCLISService).GetSQLData("INSERT INTO dbo.SYS_TableLog(AddUserDR,AddDate,AddTime,ClientIPAddress,Remark,ActData,Action,RecordID,TableDR) values('113','20190929','41819','','','aaa','U','','213')")
/// 得到SQL数据底层。用M模拟Ado底层
ClassMethod GetSQLData(SQLText As %String(MAXLEN=100000), Param As %String(MAXLEN=100000), Session As %String(MAXLEN=100000)) As %GlobalCharacterStream [ WebMethod ]
{
	s SQLText=$g(SQLText),Session=$g(Session),Param=$g(Param)
    //i SQLText["IDP_Result" s ^TMP("exesql")=$lb(SQLText,Param,Session)
    s DBCurDateTime=$h
	s $ZTrap = "ErrorHandler"
	
	//xss检测
	s ParaUp=$zcvt(SQLText_Param_Session,"U")
	s HasXss=0
	i (ParaUp["<SCRIPT>")&&(ParaUp["</SCRIPT>") d
	.s HasXss=1
	.s ClientIP=##Class(LIS.Util.Common).GetClientIP()
	.s Err="系统警告:"_ClientIP_"你正在尝试XSS攻击,你的信息已经被系统记录!请不要危害系统安全。"
	.s objStream=##Class(LIS.Util.Response).GetReturn("","-1",##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(Err))
    .s ^LISXSS($h)=$lb(ClientIP,SQLText,Param,Session)
    i (ParaUp["<")&&(ParaUp[">")&&(ParaUp["ALERT(")&&(ParaUp[")") d
	.s HasXss=1
	.s ClientIP=##Class(LIS.Util.Common).GetClientIP()
	.s Err="系统警告:"_ClientIP_"你正在尝试XSS攻击,你的信息已经被系统记录!请不要危害系统安全。"
	.s objStream=##Class(LIS.Util.Response).GetReturn("","-1",##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(Err))
    .s ^LISXSS($h)=$lb(ClientIP,SQLText,Param,Session)
    i HasXss=1 q objStream
    
	//剔除不可见字符
	i Session=$c(0) s Session=""
	s Xml=""
	s Err=""
	s FuncRet=""
	s OutPut=""
	s IsProcedure=0
	s RowCount=0
	s exeret="" 
	//数据库当前时间
	s DBCurDateTime=""
	//返回结果
 	s objStream=##class(%GlobalCharacterStream).%New()
    d objStream.Write("<Response>")
	//存储过程
	i SQLText["$StoredProcedure^" d
	.s IsProcedure=1
	.s ProcedureInfo=$p(SQLText,"^",2)
	.s ClassName=$p(ProcedureInfo,"_",1)
	.s FuncName=$p(ProcedureInfo,"_",2)
	.//防止M死循环部分******************************************
    .//记录当前进程开始时间,杀掉超时进程
    .s DBCurDateTime=##Class(LIS.WS.DHCLISServiceBase).DealOldJob(ClassName,FuncName,Param,Session)
	.//防止M死循环部分******************************************
	.//存所有解析参数
	.s AllSqlPara=""
	.s FuncDoStr="(AllSqlPara,FuncRet,OutPut) s FuncRet=$CLASSMETHOD("""_ClassName_""","""_FuncName_"""" 
	.f pi=1:1:$l(Param,"$ZLPS$") d
	..s CurPara=$p(Param,"$ZLPS$",pi)
	..//把参数组装进list,解决单引号和双引号冲突问题
	..s AllSqlPara=AllSqlPara_$lb(CurPara)
	..i pi=$l(Param,"$ZLPS$") d
	...s OutPut=CurPara
	...s FuncDoStr=FuncDoStr_",.OutPut"
	..e  d
	...s FuncDoStr=FuncDoStr_",$lg(AllSqlPara,"_pi_")"
	.s FuncDoStr=FuncDoStr_")"
 	.x (FuncDoStr,AllSqlPara,.FuncRet,.OutPut)
 	.s exeret="1"
 	.//删除记录
 	.k ^TMPLISCore("DBCARE",DBCurDateTime,$j)
 	.//执行SQL
	e  d
	.Set rset = ##class(%ResultSet).%New()
	.Do rset.Prepare(SQLText)
	.//传入了SQL参数,组装SQL参数,防止拼串的SQL注入
	.i $l(Session) d
	..s AllSqlPara=""
	..s SqlDoStr="(AllSqlPara,rset,exeret) s exeret=rset.Execute(" 
	..f pi=1:1:$l(Session,"$ZLPS$") d
	...s CurPara=$p(Session,"$ZLPS$",pi)
	...//把参数组装进list,解决单引号和双引号冲突问题
	...s AllSqlPara=AllSqlPara_$lb(CurPara)
	...i pi=1 d
	....s SqlDoStr=SqlDoStr_"$lg(AllSqlPara,"_pi_")"
	...e  d
	....s SqlDoStr=SqlDoStr_",$lg(AllSqlPara,"_pi_")"
	..s SqlDoStr=SqlDoStr_")"
	..x (SqlDoStr,AllSqlPara,.rset,.exeret)
	.e  s exeret=rset.Execute()
	.//后续解析数据
	.i exeret'="1" s exeret="错误信息:"_rset.%Message_" SQL代码:"_rset.%SQLCODE_" SQL语句:"_SQLText
	.s PageSize=$p(Param,"^",1)
	.s PageIndex=$p(Param,"^",2)
	.s (SortName, SortType)=""
	.s Xml=##class(LIS.Util.DataSetXML).DataSetToSortStream(rset , SortName, SortType, PageSize, PageIndex, .RowCount,"",objStream)
    i IsProcedure=0 d
    .i exeret'="1" d
    ..s Err=##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(exeret)
    ..s exeret=-1
    .//返回主键
    .e  d
    ..i $zcvt(SQLText,"U")["INSERT " d 
    ...s RetRowID=rset.%RowID
    ...i '$l(RetRowID) s RetRowID=%ROWID
    ...d objStream.Write("<RowID>"_RetRowID_"</RowID>")
    e  d
    .d objStream.Write("<SQLResult><SQL><FunRet>"_##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(FuncRet)_"</FunRet></SQL></SQLResult>")
    
    d objStream.Write("<RetVal>"_exeret_"</RetVal><Error>"_Err_"</Error><OutPut>"_##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(OutPut)_"</OutPut><RowCount>"_RowCount_"</RowCount>")
    d objStream.Write("</Response>")
	i IsProcedure=0 s ret=rset.Close()
	//有事务没完成记录日志
	i $TLEVEL>0 d
	.s ^LISCoreKillJobInfo($h,"开放事务")=$lb(SQLText,"ADO模式",Param,Session)
	//有事务没完成
	i $TLEVEL>0 TROLLBACK  q ##Class(LIS.Util.Response).GetReturn("","-1",##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(SQLText_"的执行返回前事务层级不为0,存在开放性事务!"))
 	q objStream
 	
ErrorHandler 
	//删除记录
	i $l(DBCurDateTime) k ^TMPLISCore("DBCARE",DBCurDateTime,$j)
    s Err=$tr("执行SQL异常:"_$tr($ZERROR,"^","--")_".错误代码:"_$ECODE,"<>") 
    //有事务没完成记录日志
	i $TLEVEL>0 d
	.s ^LISCoreKillJobInfo($h,"开放事务")=$lb(SQLText,"ADO模式",Param,Session)
    //有事务没完成
	i $TLEVEL>0 TROLLBACK  s Err=Err_SQLText_"的执行返回前事务层级不为0,存在开放性事务!"
    s objStream=##Class(LIS.Util.Response).GetReturn("","-1",##Class(LIS.WS.BLL.DHCDataJSON).DealForXML(Err))
    Quit objStream
}

/// 处理申明式事务
/// 框架按申明调用为申明式事务
/// 业务方法主动调用为委托式事务
/// w ##Class(LIS.WS.DHCLISServiceBase).DeclarativeTrans("")
/// 声明式事务在方法开始加上以下申明
/// //LIS申明式事务申明,方法头加上该申明,系统调M自动托管事务,抛异常就退出事务*************************************************************************
/// 	i '$d(^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")) d
/// 	.s ^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")=""
/// 	//去除申明式事务,就注释上面两行,放开注释下一行
/// 	//k ^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")
/// 	//异常抛出示例
/// 	//THROW ##class(%Exception.SystemException).%New("名称","代码",,"抛异常回滚事务带回的返回信息")
/// 	//LIS申明式事务申明,方法头加上该申明,系统调M自动托管事务,抛异常就退出事务*************************************************************************
ClassMethod DeclarativeTrans(ClassName, FuncName, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, Sessions, Output RowCount As %String, IsSelfStart, Output Err As %String) As %String
{
	s ClassName=$g(ClassName),FuncName=$g(FuncName),P0=$g(P0),P1=$g(P1),P2=$g(P2),P3=$g(P3),P4=$g(P4),P5=$g(P5),P6=$g(P6),P7=$g(P7),P8=$g(P8),P9=$g(P9),P10=$g(P10),P11=$g(P11),P12=$g(P12),P13=$g(P13),Sessions=$g(Sessions),RowCount=$g(RowCount)
	s IsSelfStart=$g(IsSelfStart)
	s Err=$g(Err)
	s err=""
	set ret=""
	//先记录开始事务之前的事务层级
	s StartTLEVEL=$TLEVEL
	s OldNameSpace=$NAMESPACE
	try
	{
		//没强调要自己开启事务的就开启事务
		i IsSelfStart'="1" TSTART
		s ret=$CLASSMETHOD(ClassName,FuncName,P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,Sessions,.RowCount)
		//防止别人调我方事务被回滚
		i ($TLEVEL-StartTLEVEL)>1 TROLLBACK  s err="-1^"_ClassName_"的方法"_FuncName_"发生开放性事务,请联系开发!"	
		//判断有事务,万一标明要自己开始事务实际没到那个开启
		i ('$l(err)),($TLEVEL>0) TCOMMIT
		e  s ret=err
	}
	catch ex
	{
		//只回滚到开启前层级
		i ($TLEVEL-StartTLEVEL)>1 d
		.f ti=1:1:($TLEVEL-StartTLEVEL) d
		..TROLLBACK 1
		else  TROLLBACK //捕获异常回滚全部事务
		i ex.Code="D" d
		.s ret=ex.Data
		e  d
		.s ret="-1^"_$ZERROR
		s Err=ret
	}
	//检测命名空间不同就切回来
    i ($NAMESPACE'=OldNameSpace) s $NAMESPACE=OldNameSpace
	q ret
}

/// 处理死循环进程
/// w ##Class(LIS.WS.DHCLISServiceBase).DealOldJob("")
ClassMethod DealOldJob(ClassName, FuncName, Param As %String(MAXLEN=99999999), Session)
{
	//防止M死循环部分******************************************
    //结束运行10分钟还没完成的进程
    s DBCurDateTime=$h
    
    //杀进程,默认配置到任务执行
    d ..KillOldJob()
    //统计唯一键
    s Session=$g(Session)
    s StateUniqueKey=$p(Session,"^",12)
    //统计关注
    i $l(StateUniqueKey) d
    .s ^TMPLISCore("STATECARE",DBCurDateTime,$j)=StateUniqueKey
    //记录当前进程开始时间,杀掉超时进程
	s ^TMPLISCore("DBCARE",DBCurDateTime,$j)=$lb(ClassName,FuncName,Param,Session)
	
	//防止M死循环部分******************************************
	q DBCurDateTime
}

/// 杀死进程,配置任务
/// w ##Class(LIS.WS.DHCLISServiceBase).KillOldJob()
ClassMethod KillOldJob()
{
	s DBCurDateTime=$h
    s DBCurDate=$p(DBCurDateTime,",",1)
	s DBCurTime=$p(DBCurDateTime,",",2)
	//检测节点数
	s DBCheakNum=0
	
	SET $ZTRAP="ERRHander"
	//每个查询有义务检测时间最久的5个进程,超过时间就接收
	s JobRunDateTime="" f  s JobRunDateTime=$o(^TMPLISCore("DBCARE",JobRunDateTime)) q:(JobRunDateTime="")||(DBCheakNum>10)  d
    .s JobID="" f  s JobID=$o(^TMPLISCore("DBCARE",JobRunDateTime,JobID)) q:(JobID="")||(DBCheakNum>10)  d
    ..s DBCheakNum=DBCheakNum+1
    ..s JobRunDate=$p(JobRunDateTime,",",1)
    ..s JobRunTime=$p(JobRunDateTime,",",2)
    ..//最长运行时间
	..s MaxRunTime=600
	..//统计限定执行30分钟
    ..i $d(^TMPLISCore("STATECARE",JobRunDateTime,JobID)) s MaxRunTime=1800
    ..//超过最长分钟的缓存删掉,防止死循环
	..i (DBCurDate=JobRunDate),(DBCurTime-JobRunTime>MaxRunTime) d
	...//存入杀进程信息
	...s ^LISCoreKillJobInfo(JobRunDateTime,JobID)=$g(^TMPLISCore("DBCARE",JobRunDateTime,JobID))
	...i $d(^TMPLISCore("DBCARE",JobRunDateTime,JobID)) d
	....k ^TMPLISCore("DBCARE",JobRunDateTime,JobID)
	....i $d(^TMPLISCore("STATECARE",JobRunDateTime,JobID)) k ^TMPLISCore("STATECARE",JobRunDateTime,JobID)
    ....s Stop=##Class(LIS.WS.DHCLISServiceBase).KillJobByID(JobID)
	...//给前台放入消息
	...s objMsg=##class(dbo.OTMsgStock).%New()
	...s objMsg.AddDate=$zd(+$h,8)
	...s objMsg.AddTime=$p($h,",",2)
	...s objMsg.SendDate=$zd(+$h,8)
	...s objMsg.SendTime=$p($h,",",2)
	...s objMsg.AddUser=""
	...s objMsg.SendUser="dhcc"
	...s objMsg.PerUser="SYS"
	...s objMsg.MsgType="Crisis"
	...s objMsg.Msg="{""type"": ""系统警告-刚重启数据库的忽略"", ""info"": """_$p($h,",",2)_"结束异常进程,^LISCoreKillJobInfo="_$lg(^LISCoreKillJobInfo(JobRunDateTime,JobID),1)_","_$lg(^LISCoreKillJobInfo(JobRunDateTime,JobID),2)_",请联系开发排查"", ""dealurl"": """" }"
	...s objMsg.BllType="JOB"
	...s objMsg.BllID=JobID
	...s objMsg.IsReply=0
	...s sc=objMsg.%Save()
    ..//超过最长分钟的缓存删掉
    ..i (DBCurDate>JobRunDate) d
    ...s TimeCha=((DBCurDate-JobRunDate)*3600*24)+((DBCurTime-JobRunTime))
    ...i TimeCha>MaxRunTime d
    ....//存入杀进程信息
	....s ^LISCoreKillJobInfo(JobRunDateTime,JobID)=$g(^TMPLISCore("DBCARE",JobRunDateTime,JobID))
	....i $d(^TMPLISCore("DBCARE",JobRunDateTime,JobID)) d
	.....k ^TMPLISCore("DBCARE",JobRunDateTime,JobID)
	.....i $d(^TMPLISCore("STATECARE",JobRunDateTime,JobID)) k ^TMPLISCore("STATECARE",JobRunDateTime,JobID)
	.....s Stop=##Class(LIS.WS.DHCLISServiceBase).KillJobByID(JobID)
	....//给前台放入消息
	....s objMsg=##class(dbo.OTMsgStock).%New()
	....s objMsg.AddDate=$zd(+$h,8)
	....s objMsg.AddTime=$p($h,",",2)
	....s objMsg.SendDate=$zd(+$h,8)
	....s objMsg.SendTime=$p($h,",",2)
	....s objMsg.AddUser=""
	....s objMsg.SendUser="dhcc"
	....s objMsg.PerUser="SYS"
	....s objMsg.MsgType="Crisis"
	....s objMsg.Msg="{""type"": ""系统警告-刚重启数据库的忽略"", ""info"": """_$p($h,",",2)_"结束异常进程,^LISCoreKillJobInfo="_$lg(^LISCoreKillJobInfo(JobRunDateTime,JobID),1)_","_$lg(^LISCoreKillJobInfo(JobRunDateTime,JobID),2)_",请联系开发排查"", ""dealurl"": """" }"
	....s objMsg.BllType="JOB"
	....s objMsg.BllID=JobID
	....s objMsg.IsReply=0
	....s sc=objMsg.%Save()
	q ""
	
ERRHander
 q ""
}

}

测试启动仪器控制的进程是不是调用进程,防止误杀仪器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

申明式事务
Spring给了一个约定(AOP开发也给了我们一个约定),如果使用的是声明式事务,那么当你的业务方法不发生异常(或者发生异常,但该异常也被配置信息允许提交事务)时,Spring就会让事务管理器提交事务,而发生异常(并且该异常不被你的配置信息所允许提交事务)时,则让事务管理器回滚事务。

Cache的M语言作为比较古老的语言,缺乏Java,C#这些语言很多特性。我们在写事务的时候都是自己开启事务,有问题回滚事务,成功提交事务。在事务的处理上各个人写法参差不齐,很容易导致事务没回滚或者提交。

比如以下一个正常的事务,是不是很完美?其实不对,如果别人调用我这个方法,他的事务就会被回滚掉

    try
	{
		//开启事务
		TSTART
		s ret=$CLASSMETHOD(ClassName,FuncName,P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,Sessions,.RowCount)
		i $TLEVEL>1 TROLLBACK  s err="-1^"_ClassName_"的方法"_FuncName_"发生开放性事务,请联系开发!"	
		i '$l(err) TCOMMIT
		e  s ret=err
	}
	catch ex
	{
		//捕获异常回滚事务
		TROLLBACK
		s ret=ex.Data
	}

但是随着业务逻辑的复杂,可能有错误分支没回滚事务,或者没走到提交事务逻辑。这样就可能锁表或者后期数据被回滚。业务分支越复杂出错概率越大。

为此引入申明式事务概念进入M,使得需要事务的方法只需要在方法开始按格式加一段申明代码该方法事务由公共方法托管,从而达到业务与事务分离和事务安全性。

约定申明

Class LIS.WS.DeclarativeTransTest Extends %RegisteredObject
{

// 以下为带事务调试

// w ##Class(LIS.WS.DHCLISService).DeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans","")

ClassMethod DeclarativeTrans(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, Sessions, Output RowCount As %String) As %String
{
	//LIS申明式事务申明,方法头加上该申明,系统调M自动托管事务,抛异常就退出事务*************************************************************************
	i '$d(^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")) d
	.s ^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")=""
	//去除申明式事务,就注释上面两行,放开注释下一行
	//k ^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")
	//异常抛出示例
	//THROW ##class(%Exception.General).%New("名称","代码",,"抛异常回滚事务带回的返回信息")
	//LIS申明式事务申明,方法头加上该申明,系统调M自动托管事务,抛异常就退出事务*************************************************************************
	
	
	//以下写具体业务逻辑,如果有不需要提交事务情况直接按下面那样抛异常
	
	//业务逻辑
	//业务逻辑
	//业务逻辑
	//失败异常
	THROW ##class(%Exception.General).%New("名称","代码",,"抛异常回滚事务带回的返回信息")
	
	//成功返回
	q "1"
}

}

事务托管关键点

/// 处理申明式事务
/// 框架按申明调用为申明式事务
/// 业务方法主动调用为委托式事务
/// w ##Class(LIS.WS.DHCLISServiceBase).DeclarativeTrans("")
/// 声明式事务在方法开始加上以下申明
/// //LIS申明式事务申明,方法头加上该申明,系统调M自动托管事务,抛异常就退出事务*************************************************************************
/// 	i '$d(^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")) d
/// 	.s ^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")=""
/// 	//去除申明式事务,就注释上面两行,放开注释下一行
/// 	//k ^LISDeclarativeTrans("LIS.WS.DeclarativeTransTest","DeclarativeTrans")
/// 	//异常抛出示例
/// 	//THROW ##class(%Exception.SystemException).%New("名称","代码",,"抛异常回滚事务带回的返回信息")
/// 	//LIS申明式事务申明,方法头加上该申明,系统调M自动托管事务,抛异常就退出事务*************************************************************************
ClassMethod DeclarativeTrans(ClassName, FuncName, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, Sessions, Output RowCount As %String, IsSelfStart, Output Err As %String) As %String
{
	s ClassName=$g(ClassName),FuncName=$g(FuncName),P0=$g(P0),P1=$g(P1),P2=$g(P2),P3=$g(P3),P4=$g(P4),P5=$g(P5),P6=$g(P6),P7=$g(P7),P8=$g(P8),P9=$g(P9),P10=$g(P10),P11=$g(P11),P12=$g(P12),P13=$g(P13),Sessions=$g(Sessions),RowCount=$g(RowCount)
	s IsSelfStart=$g(IsSelfStart)
	s Err=$g(Err)
	s err=""
	set ret=""
	//先记录开始事务之前的事务层级
	s StartTLEVEL=$TLEVEL
	s OldNameSpace=$NAMESPACE
	try
	{
		//没强调要自己开启事务的就开启事务
		i IsSelfStart'="1" TSTART
		s ret=$CLASSMETHOD(ClassName,FuncName,P0,P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,Sessions,.RowCount)
		//防止别人调我方事务被回滚
		i ($TLEVEL-StartTLEVEL)>1 TROLLBACK  s err="-1^"_ClassName_"的方法"_FuncName_"发生开放性事务,请联系开发!"	
		//判断有事务,万一标明要自己开始事务实际没到那个开启
		i ('$l(err)),($TLEVEL>0) TCOMMIT
		e  s ret=err
	}
	catch ex
	{
		//只回滚到开启前层级
		i ($TLEVEL-StartTLEVEL)>1 d
		.f ti=1:1:($TLEVEL-StartTLEVEL) d
		..TROLLBACK 1
		else  TROLLBACK //捕获异常回滚全部事务
		i ex.Code="D" d
		.s ret=ex.Data
		e  d
		.s ret="-1^"_$ZERROR
		s Err=ret
	}
	//检测命名空间不同就切回来
    i ($NAMESPACE'=OldNameSpace) s $NAMESPACE=OldNameSpace
	q ret
}

底层对接部分

以上两种设计思想都是针对M语言的问题出发,适应于整个基于Cache库的开发,希望对别的块有所启发。M虽然低级,也能有架构。应该从技术层面上去避免问题,而不是一味把关注点放在处罚上。

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小乌鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值