乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      使用Drools和JPA實(shí)現(xiàn)持續(xù)的實(shí)時(shí)數(shù)據(jù)分析

       LibraryPKU 2014-09-20

      企業(yè)開發(fā)人員按照管理復(fù)雜工作流、業(yè)務(wù)規(guī)則和業(yè)務(wù)智能來(lái)分派任務(wù),這樣可以快速實(shí)現(xiàn)企業(yè)平臺(tái)的價(jià)值,該平臺(tái)集成了工作流引擎、企業(yè)服務(wù)總線 (ESB) 和規(guī)則引擎。迄今為止,這個(gè)出色的平臺(tái)已經(jīng)被 IBM WebSphere? Process Server/WebSphere Enterprise Service Bus(參見 參考資料)和 Oracle SOA Suite 之類的商用產(chǎn)品填滿了。來(lái)自 Boss Community 的 Drools 5 是一種開源替代方案,它通過(guò)一組統(tǒng)一的 API 和一個(gè)共享的、有狀態(tài)的知識(shí)會(huì)話來(lái)無(wú)縫集成 jBPM 工作流引擎和規(guī)則引擎。

      面向初學(xué)者

      請(qǐng)注意,本文假設(shè)您熟悉 Spring 平臺(tái)、Java Persistence API 和 Drools 的基本原理。請(qǐng)參閱 參考資料,了解有關(guān)這些主題的更多介紹性文章。

      Drools 5 的 Business Logic 集成平臺(tái)主要包括 Drools Expert 和 Drools Fusion,這兩項(xiàng)共同組成了平臺(tái)的規(guī)則引擎和用于復(fù)雜事件處理/時(shí)態(tài)推理的基礎(chǔ)架構(gòu)。本文的樣例應(yīng)用程序是根據(jù)這些核心特性構(gòu)建的。請(qǐng)參閱 參考資料,了解有關(guān) Drools 5 中其他可用程序包的更多信息。

      Drools 5 中的 POJO

      傳 統(tǒng)的 Java 對(duì)象 (POJO) 是在 Spring 框架中首次以引人注目的方式實(shí)現(xiàn)的。POJO 以及依賴注入 (DI) 和面向方面的編程 (AOP) 共同標(biāo)志著向簡(jiǎn)單性的回歸,這種簡(jiǎn)單性有效地促進(jìn) Spring 成為開發(fā) Web 應(yīng)用程序的一種行業(yè)標(biāo)準(zhǔn)。POJO 的采用已經(jīng)從 Spring 流向 EJB 3.0 和 JPA,然后再流向 XML-to-Java 綁定技術(shù)(比如 JAXB 和 XStream)。最近,POJO 已通過(guò) Hibernate Search 集成到了全文搜索引擎 Lucene 中(參閱 參考資料)。

      如今,由于這些增加的改進(jìn),應(yīng)用程序的 POJO 數(shù)據(jù)模型可以在多個(gè)層上進(jìn)行傳播,并直接通過(guò) Web 頁(yè)面或 SOAP/REST Web 服務(wù)端點(diǎn)進(jìn)行公開。作為一種編程模型,POJO 既經(jīng)濟(jì)高效又屬于非侵入性的,這為開發(fā)人員在簡(jiǎn)化企業(yè)架構(gòu)時(shí)節(jié)約了不少時(shí)間。

      現(xiàn)在,Drools 5 通過(guò)允許直接將 POJO 作為事實(shí) (fact) 直接插入知識(shí)會(huì)話(knowledge session)中,或是插入一個(gè)稱為 “工作內(nèi)存” 的規(guī)則引擎中,將 POJO 編程簡(jiǎn)單性應(yīng)用于下一個(gè)級(jí)別。本文介紹了一種既經(jīng)濟(jì)高效又屬于非侵入性的方法,這種方法將 JPA 實(shí)體作為 Drools 工作內(nèi)存中的事實(shí)來(lái)進(jìn)行操作。持續(xù)的實(shí)時(shí)數(shù)據(jù)分析從來(lái)不會(huì)這么簡(jiǎn)單。

      Drools 編程挑戰(zhàn)

      許多醫(yī)療服務(wù)提供商使用案例管理系統(tǒng)作為跟蹤醫(yī)療記錄(比如護(hù)理、處方和評(píng)估)的一種經(jīng)濟(jì)高效的方法。我們的示例程序(基于這樣一種系統(tǒng))具有以下流程和需求:

      • 案例在系統(tǒng)內(nèi)的所有臨床醫(yī)生之間循環(huán)。
      • 臨床醫(yī)生每周至少負(fù)責(zé)一個(gè)評(píng)估任務(wù),或?qū)⑼ㄖl(fā)送給臨床醫(yī)生的監(jiān)管員。
      • 系統(tǒng)給臨床醫(yī)生自動(dòng)安排評(píng)估任務(wù)。
      • 如果案例未評(píng)估超過(guò) 30 天,則向案例組中的所有臨床醫(yī)生發(fā)送提醒。
      • 如果沒有響應(yīng),系統(tǒng)會(huì)采取系統(tǒng)的業(yè)務(wù)規(guī)則定義的措施,比如通知出現(xiàn)問(wèn)題的臨床醫(yī)生組并提出另一項(xiàng)計(jì)劃。

      為 該用例選擇一個(gè)業(yè)務(wù)流程管理 (BPM) 工作流和規(guī)則引擎是有一定道理的:系統(tǒng)使用數(shù)據(jù)剖析/分析規(guī)則(已在上述列表中用斜體字標(biāo)出),將每個(gè)案例用作在 jBPM 中長(zhǎng)期運(yùn)行的一個(gè)流程/工作流,而且我們可以使用一個(gè) Drools Planner 來(lái)滿足自動(dòng)安排的需求。出于本文的目的,我們將只關(guān)注程序的業(yè)務(wù)規(guī)則。我們還要介紹的是系統(tǒng)需求,在滿足規(guī)則條件時(shí)立即實(shí)時(shí)生成提醒和通知。因此這是一個(gè) 持續(xù)的實(shí)時(shí)數(shù)據(jù)分析用例。

      清單 1 顯示了在我們的系統(tǒng)中聲明的三個(gè)實(shí)體類:MemberCase、ClinicianCaseSupervision

      清單 1. 實(shí)體類
      @Entity
      @EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class})
      public class MemberCase implements Serializable 
      {
        private Long id; // pk
        private Date startDtm;
        private Date endDtm;
        private Member member; // not null (memberId)
        private List<CaseSupervision> caseSupervisions = new ArrayList<CaseSupervision>();
        //...
      }
       
      @Entity
      @EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class})
      public class Clinician implements Serializable 
      { 
        private Long id; // pk
        private Boolean active;
        private List<CaseSupervision> caseSupervisions = new ArrayList<CaseSupervision>();
      	//...
      }
      
      @Entity
      @EntityListeners({SupervisionStreamWorkingMemoryPartitionEntityListener.class})
      public class CaseSupervision implements Serializable 
      { 
        private Long id; // pk
        private Date entryDtm;
        private MemberCase memberCase;
        private Clinician clinician;
        //...
      }

      MemberCase 中的每個(gè)實(shí)例代表一個(gè)病歷。Clinician 代表機(jī)構(gòu)中的臨床醫(yī)生。臨床醫(yī)生每次進(jìn)行案例評(píng)估時(shí)會(huì)產(chǎn)生一個(gè) CaseSupervision 記錄。同時(shí),這三個(gè)實(shí)體是將要定義的業(yè)務(wù)規(guī)則中的事實(shí)類型。還要注意的是,上述 CaseSupervision 被聲明為 Drools 中的一個(gè)事件類型。

      從應(yīng)用程序的角度來(lái)看,我們可以從系統(tǒng)的任何地方、在不同的屏幕上、在不同的工作流中修改這三種類型的實(shí)體。我們甚至可以使用 Spring Batch 這樣的工具來(lái)批量更新實(shí)體。然而,出于本例的考慮,讓我們假設(shè)將只通過(guò) JPA 持久上下文來(lái)更新實(shí)體。

      注意,樣例應(yīng)用程序是一個(gè) Spring-Drools 集成,它使用 Maven 來(lái)完成構(gòu)建。本文稍后將考慮一些配置細(xì)節(jié),但是您可以隨時(shí) 下載源 zip?,F(xiàn)在,讓我們考慮一些使用 Drools 5 的概念特性。

      事實(shí)和 FactHandle

      規(guī)則引擎的一般概念是:事實(shí) (fact) 是規(guī)則所依賴的數(shù)據(jù)對(duì)象。在 Drools 中,事實(shí)是從應(yīng)用程序獲得且斷言為引擎的工作內(nèi)存的任意 Java bean?;蛘哒f(shuō),就像在 JBoss Drools 參考手冊(cè) 中撰寫的那樣:

      規(guī) 則引擎根本沒有 “克隆” 事實(shí),它是一天結(jié)束時(shí)的所有引用/指針 (pointer)。事實(shí)是您的應(yīng)用程序數(shù)據(jù)。沒有 getter 和 setter 的 Strings 和其他類不是有效的 Fact,不能和 Field Constraints 一起使用,F(xiàn)ield Constraints 依靠 getter 和 setter 的 JavaBean 標(biāo)準(zhǔn)與對(duì)象進(jìn)行交互。

      除非您在規(guī)則之上已經(jīng)指定了關(guān)鍵字 no-looplock-on-active,否則在工作內(nèi)存中發(fā)生事實(shí)更改時(shí),不能在任何時(shí)候重新評(píng)估 Drools 規(guī)則引擎中的規(guī)則。您還可以使用 @PropertyReactive@watch 注釋來(lái)指定事實(shí)屬性,Drools 應(yīng)該監(jiān)視這些屬性來(lái)應(yīng)對(duì)更改。Drools 會(huì)忽略對(duì)事實(shí)的其他所有屬性的更新。

      出于實(shí)際維護(hù)的目的,有三種方法來(lái)安全更新 Drools 工作內(nèi)存中的事實(shí)

      1. 在 Drools 語(yǔ)法中,右邊 (RHS) 是規(guī)則的操作/結(jié)果部分,您可以在 modify 塊內(nèi)對(duì)其進(jìn)行更新。將一個(gè)事實(shí)更改為將要激活的規(guī)則的結(jié)果時(shí),會(huì)使用這個(gè)方法。
      2. 從外部通過(guò) Java 類中的 FactHandle;用于由應(yīng)用程序 Java 代碼執(zhí)行的事實(shí)更改。
      3. Fact 類實(shí)現(xiàn) PropertyChangeSupport,就像 JavaBeans 規(guī)范定義的那樣;使用此方法將 Drools 注冊(cè)為 Fact 對(duì)象的 PropertyChangeListener

      作為安靜的觀察者,我們的規(guī)則不會(huì)更新 Drools 工作內(nèi)存中的任何 JPA 實(shí)體事實(shí);相反,它們會(huì)將邏輯事實(shí)生成為推理結(jié)果。(參見下列的 清單 6。)但是,更新規(guī)則中的 JPA 實(shí)體時(shí)需要特別注意,因?yàn)楦碌膶?shí)體可能處于分離狀態(tài),或者沒有事務(wù)或只讀事務(wù)與當(dāng)前線程有關(guān)聯(lián)。因此,對(duì)實(shí)體所做的更改將不會(huì)保存到數(shù)據(jù)庫(kù)中。

      盡管事實(shí)對(duì)象是因?yàn)橐枚粋鬟f,Drools(與 JPA/Hibernate 不同)不能跟蹤超出規(guī)則之外的事實(shí)更改。您可以通過(guò)使用 FactHandle 來(lái)通知 Drools 有關(guān)在應(yīng)用程序 Java 代碼中所做的事實(shí)更改,從而避免產(chǎn)生不一致的規(guī)則推理結(jié)果。Drools 接著會(huì)對(duì)規(guī)則進(jìn)行適當(dāng)?shù)闹匦略u(píng)估。FactHandle 是表示您在工作內(nèi)存中斷言的事實(shí)對(duì)象的標(biāo)記。這就是您希望修改或取消一個(gè)事實(shí)時(shí)與工作內(nèi)存的正常交互方式。在樣例應(yīng)用程序(清單 2清單 3)中,我們使用 FactHandle 來(lái)操作工作內(nèi)存中的實(shí)體事實(shí)。

      您可以通過(guò)實(shí)現(xiàn) PropertyChangeSupport(它捕獲了對(duì) bean 的屬性所做的每一項(xiàng)更改)來(lái)解決 Drools 無(wú)法跟蹤事實(shí)更改的問(wèn)題。但是,請(qǐng)記住,這也是隨后由于執(zhí)行頻繁的規(guī)則重新評(píng)估而需要解決的性能問(wèn)題。

      使用 JPA 實(shí)體作為事實(shí)

      您可以通過(guò) POJO 事實(shí)將 JPA 實(shí)體作為域數(shù)據(jù)對(duì)象插入到 Drools 的工作內(nèi)存中。這樣做可以讓您避免對(duì) Value Object/DTO 層以及 JPA 實(shí)體和 DTO 之間的相應(yīng)轉(zhuǎn)換層進(jìn)行數(shù)據(jù)建模。

      將 實(shí)體用作事實(shí)會(huì)簡(jiǎn)化應(yīng)用程序代碼,您必須額外注意 “實(shí)體-生命周期” 階段。實(shí)體事實(shí)應(yīng)當(dāng)保存為受管(持久)狀態(tài)或分離狀態(tài)。永遠(yuǎn)不要將臨時(shí)的實(shí)體插入到 Drools 工作內(nèi)存中,因?yàn)樗鼈冞€未保存到數(shù)據(jù)庫(kù)中。同樣,應(yīng)當(dāng)從工作內(nèi)存中收回已刪除的實(shí)體。否則應(yīng)用程序數(shù)據(jù)庫(kù)和規(guī)則引擎的工作內(nèi)存會(huì)不同步。

      因此,這會(huì)帶來(lái)一些嚴(yán)重的問(wèn)題:我們?nèi)绾尾拍苡行ㄖ?guī)則引擎有關(guān)通過(guò) FactHandle 在應(yīng)用程序代碼中所做的實(shí)體更改?

      命令式(Imperative)編程與 AOP 的比較

      如果想通過(guò)命令式編程的方式來(lái)應(yīng)對(duì)這個(gè)挑戰(zhàn),我們需要結(jié)束在緊鄰相應(yīng) JPA API 方法的知識(shí)會(huì)話上調(diào)用 insert()update()retract() 方法。這種方法應(yīng)該是 Drools API 的一種入侵性用法,而且應(yīng)將 spaghetti 代碼留在應(yīng)用程序中。更糟糕的是,JPA 中已更新的(臟)實(shí)體在讀取/寫入事務(wù)結(jié)束時(shí)會(huì)自動(dòng)與數(shù)據(jù)庫(kù)同步運(yùn)行,未對(duì)持久上下文進(jìn)行任何顯式調(diào)用。我們?nèi)绾尾拍軘r截這些更改并通知給 Drools?另一個(gè)選擇是,在單獨(dú)的進(jìn)程中輪詢實(shí)體更改,就像典型的商業(yè)智能 (BI) 工具所做的那樣,這會(huì)使核心業(yè)務(wù)功能保持干凈,但實(shí)現(xiàn)此操作很困難,成本較高,結(jié)果也不會(huì)是即時(shí)的。

      JPA EntityListener 是一種 AOP 攔截器,非常適合我們的用例。在 清單 2 中,我們將定義兩個(gè) EntityListener,它們會(huì)攔截對(duì)應(yīng)用程序中三種類型的實(shí)體所做的所有更改。這種方法使得 JPA 中實(shí)體 的生命周期與其在 Drools 中的生命周期不斷地保持同步。

      在 “實(shí)體-生命周期” 回調(diào)方法中,我們?yōu)榻o定的實(shí)體實(shí)例查找一個(gè) FactHandle,然后根據(jù) JPA 生命周期階段通過(guò)返回的 FactHandle 來(lái)更新或收回事實(shí)。如果 FactHandle 缺失,則會(huì)將實(shí)體作為新事實(shí) 插入到工作內(nèi)存中,以實(shí)現(xiàn)事實(shí)更新或持久保存。由于工作內(nèi)存中不存在實(shí)體,因此在調(diào)用 JPA 刪除時(shí)也沒有必要將其從工作內(nèi)存中刪除。清單 2 中所示的兩個(gè) JPA EntityListener 用于工作內(nèi)存中的兩個(gè)不同的入口點(diǎn),或者分區(qū)。第一個(gè)入口點(diǎn)在 MemberCaseClinician 之間共享,第二個(gè)入口點(diǎn)用于 CaseSupervision 事件類型。

      清單 2. EntityListeners
      @Configurable
      public class DefaultWorkingMemoryPartitionEntityListener 
      {
        @Value("#{ksession}") //unable to make @Configurable with compile time weaving work here
        private StatefulKnowledgeSession ksession;   
         
        @PostPersist
        @PostUpdate
        public void updateFact(Object entity)
        {       
          FactHandle factHandle = getKsession().getFactHandle(entity);
          if(factHandle == null)
            getKsession().insert(entity);
          else
            getKsession().update(factHandle, entity);
        }        
      		   
        @PostRemove
        public void retractFact(Object entity)
        {
          FactHandle factHandle = getKsession().getFactHandle(entity);
          if(factHandle != null)
            getKsession().retract(factHandle);
        }
       
        public StatefulKnowledgeSession getKsession() 
        {
          if(ksession != null)
          {
            return ksession;
          }
          else
          {
            // a workaround for @Configurable
            setKsession(ApplicationContextProvider.getApplicationContext()
              .getBean("ksession", StatefulKnowledgeSession.class));
            return ksession;
          }
        }
        //...
      }
       
      @Configurable
      public class SupervisionStreamWorkingMemoryPartitionEntityListener
      { 
        @Value("#{ksession}")  
        private StatefulKnowledgeSession ksession;   
      	
        @PostPersist 
        // CaseSupervision is an immutable event, 
        // thus we don’t provide @PostUpdate and @PostRemove implementations.
        public void insertFact(Object entity)
        {   
          WorkingMemoryEntryPoint entryPoint = getKsession()
            .getWorkingMemoryEntryPoint("SupervisionStream");
          entryPoint.insert(entity);
        }        
        //...
      }

      就像 AOP 一樣,清單 2 中的 EntityListener 方法使系統(tǒng)的核心業(yè)務(wù)邏輯保持干凈。注意,這種方法需要一個(gè)或多個(gè) Drools 全局知識(shí)會(huì)話來(lái)注入到兩個(gè) EntityListener 中。在本文的后面部分中,我們將一個(gè)知識(shí)會(huì)話聲明為一個(gè)單態(tài)(singleton)的 Spring bean。

      提示:共享的全局知識(shí)會(huì)話

      從本質(zhì)上來(lái)說(shuō),共享的全局知識(shí)會(huì)話使得 EntityListener 方法適合于系統(tǒng)范圍、BI 數(shù)據(jù)分析和分析需求。它同樣不適合特定于用戶流程以及在線購(gòu)物系統(tǒng)之類的規(guī)則執(zhí)行,在這些系統(tǒng)中,通常會(huì)動(dòng)態(tài)生成知識(shí)會(huì)話來(lái)處理用戶指定的數(shù)據(jù),然后將其解決。

      初始化工作內(nèi)存

      在啟動(dòng)應(yīng)用程序后,三種實(shí)體類型的所有現(xiàn)有記錄都將從數(shù)據(jù)庫(kù)預(yù)加載到用于規(guī)則執(zhí)行的工作內(nèi)存中,如 清單 3 所示。從那時(shí)起,會(huì)向工作內(nèi)存通知通過(guò)兩個(gè) EntityListener 對(duì)實(shí)體所做的任何更改。

      清單 3. 初始化工作內(nèi)存并運(yùn)行 Drools 查詢
      @Service("droolsService")
      @Lazy(false)
      @Transactional
      public class DroolsServiceImpl 
      {
        @Value("#{droolsServiceUtil}")
        private DroolsServiceUtil droolsServiceUtil;
          
        @PostConstruct
        public void launchRules()
        {
          droolsServiceUtil.initializeKnowledgeSession();
          droolsServiceUtil.fireRulesUtilHalt();    
        }
         
        public Collection<TransientReminder> findCaseReminders()
        {
          return droolsServiceUtil.droolsQuery("CaseReminderQuery", 
            "caseReminder", TransientReminder.class, null);
        }
         
        public Collection<TransientReminder> findClinicianReminders()
        {
          return droolsServiceUtil.droolsQuery("ClinicianReminderQuery", 
            "clinicianReminder", TransientReminder.class, null);
        }
      }  
       
      @Service
      public class DroolsServiceUtil
      {
        @Value("#{ksession}")
        private StatefulKnowledgeSession ksession;
                  
        @Async
        public void fireRulesUtilHalt()
        {
          try{
            getKsession().fireUntilHalt(); 
          }catch(ConsequenceException e) 
          {
            throw e;
          }
        }
         
        public void initializeKnowledgeSession()
        {  
          getKsession().setGlobal("droolsServiceUtil", this);
          syncFactsWithDatabase();
        }
      
        @Transactional //a transaction-scoped persistence context
        public void syncFactsWithDatabase()
        {
          synchronized(ksession)
          {       
            // Reset all the facts in the working memory
            Collection<FactHandle> factHandles = getKsession().getFactHandles(
              new ObjectFilter(){public boolean accept(Object object)
              {
                if(object instanceof MemberCase)
                  return true;
                return false;
              }
            });
            for(FactHandle factHandle : factHandles)
            {
              getKsession().retract(factHandle);
            }
      
            factHandles = getKsession().getFactHandles(
              new ObjectFilter(){public boolean accept(Object object)
              {
                if(object instanceof Clinician)
                  return true;
                return false;
              }
            });
            for(FactHandle factHandle : factHandles)
            {
              getKsession().retract(factHandle);
            }           
      
            WorkingMemoryEntryPoint entryPoint = getKsession()
              .getWorkingMemoryEntryPoint("SupervisionStream");
            factHandles = entryPoint.getFactHandles();
            for(FactHandle factHandle : factHandles)
            {
              entryPoint.retract(factHandle);
            }               
      
            List<Command> commands = new ArrayList<Command>();
            commands.add(CommandFactory.newInsertElements(getMemberCaseService().findAll()));
            getKsession().execute(CommandFactory.newBatchExecution(commands));
      
            commands = new ArrayList<Command>();
            commands.add(CommandFactory.newInsertElements(getClinicianService().findAll()));
            getKsession().execute(CommandFactory.newBatchExecution(commands));    
      	 
            for(CaseSupervision caseSupervision : getCaseSupervisionService().findAll())
            {
              entryPoint.insert(caseSupervision);
            }  
                 
          }
        }
       
        public <T> Collection<T> droolsQuery(String query, String variable, 
          Class<T> c, Object... args)
        {
          synchronized(ksession)
          {       
            Collection<T> results = new ArrayList<T>();
            QueryResults qResults = getKsession().getQueryResults(query, args);  
            for(QueryResultsRow qrr : qResults)
            {
              T result = (T) qrr.get("$"+variable);
              results.add(result);
            }       
            return results;
          }
        }
      }

      有關(guān) fireAllRules() 的注意事項(xiàng)

      請(qǐng)注意,在 清單 3 中,我們擁有在各個(gè) EntityListener 的回調(diào)方法中調(diào)用 fireAllRules() 的選項(xiàng)。在一個(gè)急切加載 (eager-loaded) 的 Spring bean 的 “@PostConstruct” 方法中,我只調(diào)用了一次 fireUntilHalt() 方法就簡(jiǎn)化了這一步驟。fireUtilHalt 方法應(yīng)該在單獨(dú)的線程中調(diào)用一次(查看 Spring 的 @Async 注釋),隨后不斷觸發(fā)規(guī)則激活,直至調(diào)用停止。如果沒有需要觸發(fā)的激活,fireUtilHalt 會(huì)等待將激活添加到一個(gè)激活議程組或規(guī)則流組中。

      我可以選擇在應(yīng)用程序的 Spring XML 配置文件(如下所示)中觸發(fā)規(guī)則,甚至是啟動(dòng)流程。然而,我在嘗試配置 fireUntilHalt() 方法時(shí)檢測(cè)到了一個(gè)可能的線程處理問(wèn)題。在對(duì)懶惰式加載 (lazy-loading) 實(shí)體關(guān)系進(jìn)行規(guī)則評(píng)估期間,結(jié)果是一個(gè) “數(shù)據(jù)庫(kù)連接已關(guān)閉的錯(cuò)誤”(參見高級(jí)主題)。

      Spring-Drools 集成

      現(xiàn)在,讓我們花一些時(shí)間來(lái)看看 Spring-Drools 集成的一些配置細(xì)節(jié)。清單 4 是應(yīng)用程序的 Maven pom.xml 的一個(gè)代碼段,包括用于 Drools 內(nèi)核、Drools 編譯器和 Drools Spring 集成包的依賴關(guān)系:

      清單 4. 部分 Maven pom.xml
      <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-core</artifactId>
        <version>5.4.0.Final</version>
        <type>jar</type>
      </dependency>               
      <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
        <version>5.4.0.Final</version>
        <type>jar</type>
      </dependency>
      <dependency> 
        <groupId>org.drools</groupId> 
        <artifactId>drools-spring</artifactId> 
        <version>5.4.0.Final</version> 
        <type>jar</type> 
        <exclusions>
          <!-- The dependency pom includes spring and hibernate dependencies by mistake. -->	
        </exclusions>
      </dependency>

      身份與等同性的比較

      清單 5 中,我將一個(gè)全局有狀態(tài)知識(shí)會(huì)話配置為一個(gè)單態(tài)的 Spring bean。(一個(gè)無(wú)狀態(tài)知識(shí)會(huì)話不會(huì)充當(dāng)一個(gè)持續(xù)時(shí)間很長(zhǎng)的會(huì)話,因?yàn)樗诘{(diào)用期間沒有保持其狀態(tài)。)清單 5 中需要注意的一個(gè)重要設(shè)置是 <drools:assert-behavior mode="EQUALITY" />

      在 JPA/Hibernate 中,托管實(shí)體將與身份(identity) 進(jìn)行比較,而分離的實(shí)體將與等同性(equality) 進(jìn)行比較。插入到有狀態(tài)實(shí)體會(huì)話中的實(shí)體快速?gòu)?JPA 角度分離。因?yàn)榕c單態(tài)的有狀態(tài)知識(shí)會(huì)話的生命期相比,一個(gè)事務(wù)范圍的持續(xù)上下文,甚至是一個(gè) “擴(kuò)展的” 或 “流范圍的” 持續(xù)上下文(參見 參考資料)是臨時(shí)的。每次通過(guò)不同的持續(xù)上下文對(duì)象取得的同一個(gè)實(shí)體是不同的 Java 對(duì)象。默認(rèn)情況下,Drools 使用的是身份比較。因此,當(dāng)通過(guò) ksession.getFactHandle(entity) 在工作內(nèi)存中的現(xiàn)有實(shí)體事實(shí)上查看 FactHandle 時(shí),Drools 很可能不會(huì)找到匹配項(xiàng)。為了與分離的實(shí)體相匹配,我們必須在配置文件中選擇 EQUALITY。

      清單 5. 部分 Spring applicationContext.xml
      <drools:kbase id="kbase">
        <drools:resources>
          <drools:resource  type="DRL" source="classpath:drools/rules.drl" />
        </drools:resources>
        <drools:configuration>
          <drools:mbeans enabled="true" />
          <drools:event-processing-mode mode="STREAM" />
          <drools:assert-behavior mode="EQUALITY" />
        </drools:configuration>
      </drools:kbase>
      <drools:ksession id="ksession" type="stateful" name="ksession" kbase="kbase" />

      看看應(yīng)用程序源代碼,了解更完整的配置細(xì)節(jié)。

      Drools 規(guī)則

      清單 6 定義了兩個(gè)復(fù)雜的事件處理 (CEP) 規(guī)則。除了類似 JPA 的兩個(gè)事實(shí)類型之外,MemberCaseClinician,CaseSupervision 實(shí)體類被聲明為一個(gè)事件。臨床醫(yī)生執(zhí)行的每個(gè)案例評(píng)估任務(wù)都會(huì)生成一個(gè) CaseSupervision 記錄。創(chuàng)建記錄之后,不可能一直不斷地對(duì)其進(jìn)行更改。

      清單 6 中的 Case Supervision 規(guī)則的條件可用來(lái)測(cè)試在過(guò)去的 30 天內(nèi)案例上是否已經(jīng)存在案例監(jiān)督。如果沒有,規(guī)則的結(jié)果/措施部分會(huì)生成一個(gè) TransientReminder 事實(shí)(在 清單 7 中定義),并從邏輯上將事實(shí)插入工作內(nèi)存。Clinician Supervision 規(guī)則指示,臨床醫(yī)生在過(guò)去的七天內(nèi)應(yīng)當(dāng)已經(jīng)完成至少一個(gè)案例監(jiān)督;如果沒有完成,規(guī)則的結(jié)果/措施部分會(huì)生成一個(gè)類似的 TransientReminder 事實(shí),并從邏輯上插入到工作內(nèi)存中。

      清單 6. 案例監(jiān)督規(guī)則
      package ibm.developerworks.article.drools;
      
      import ibm.developerworks.article.drools.service.*
      import ibm.developerworks.article.drools.domain.*
       
      global DroolsServiceUtil droolsServiceUtil;
      
      declare Today
        @role(event)
        @expires(24h)
      end
      
      declare CaseSupervision
        @role(event)
        @timestamp(entryDtm)
      end
      
      rule "Set Today"
        timer (cron: 0 0 0 * * ?)
        salience 99999  // optional
        no-loop
        when
        then
          insert(new Today()); 
      end
      
      rule "Case Supervision"
        dialect "mvel"
        when
          $today : Today()
          $memberCase : MemberCase(endDtm == null, startDtm before[30d] $today)
          not CaseSupervision(memberCase == $ memberCase) 
            over window:time(30d) from entry-point SupervisionStream
          then
            insertLogical(new TransientReminder($memberCase, (Clinician)null, 
              "CaseReminder", "No supervision on the case in last 30 days."));
      end
       
      query "CaseReminderQuery"
        $caseReminder : TransientReminder(reminderTypeCd == "CaseReminder")
      end
       
      rule "Clinician Supervision"
        dialect "mvel"
        when
          $clinician : Clinician()
          not CaseSupervision(clinician == $clinician) 
            over window:time(7d) from entry-point SupervisionStream
        then
          insertLogical(new TransientReminder((MemberCase)null, $clinician, 
            "ClinicianReminder", "Clinician completed no evaluation in last 7 days."));
      end
       
      query "ClinicianReminderQuery"
        $clinicianReminder : TransientReminder(reminderTypeCd == "ClinicianReminder")
      end

      請(qǐng)注意,清單 7 中所示的 TransientReminder 事實(shí)不是一個(gè) JPA 實(shí)體,而是一個(gè)常規(guī)的 POJO。

      清單 7. TransientReminder
      public class TransientReminder implements Comparable, Serializable
      {			
        private MemberCase memberCase;
        private Clinician clinician;
        private String reminderTypeCd;
        private String description;
      
        public String toString() 
        {
          return ReflectionToStringBuilder.toString(this);
        }
      
        public boolean equals(Object pObject) 
        {
          return EqualsBuilder.reflectionEquals(this, pObject);
        }
      
        public int compareTo(Object pObject) 
        {
          return CompareToBuilder.reflectionCompare(this, pObject);
        }
      
        public int hashCode() 
        {
          return HashCodeBuilder.reflectionHashCode(this);
        } 	
      }

      事實(shí)與事件的比較

      事件是使用 @timestamp、@duration@expires 之類的時(shí)態(tài)元數(shù)據(jù)進(jìn)行裝飾的事實(shí)。事實(shí)和事件之間最重要的區(qū)別是,事件在 Drools 上下文中是不可變的。如果一個(gè)事件受更改的制約,那么更改(描述為 “事件數(shù)據(jù)補(bǔ)充”)不應(yīng)當(dāng)影響規(guī)則執(zhí)行的結(jié)果。這就是我們?cè)?CaseSupervisionEntityListener 中只監(jiān)視 @PostPersist 實(shí)體生命周期階段的原因(參見 清單 2)。

      Drools 對(duì) Sliding Windows 協(xié)議的支持使得事件對(duì)時(shí)態(tài)推理特別有用。滑動(dòng)窗口 是為感興趣的事件制定作用域的一種方式,就好像它們屬于一個(gè)不斷移動(dòng)的窗口一樣。兩種最常見的滑動(dòng)窗口實(shí)現(xiàn)是基于時(shí)間的窗口和基于長(zhǎng)度的窗口。

      清單 6 中所示的樣例規(guī)則中,over window:time(30d) 建議,過(guò)去 30 天中創(chuàng)建的 CaseSupervision 事件由規(guī)則引擎進(jìn)行評(píng)估。一旦過(guò)了 30 天,不可變的事件將永遠(yuǎn)不會(huì)再次進(jìn)入到窗口中,而且 Drools 將自動(dòng)從工作內(nèi)存中收回這些事件,并對(duì)相應(yīng)的規(guī)則進(jìn)行重新評(píng)估。由于事件是不可變的,所以 Drools 會(huì)自動(dòng)管理事件生命周期。因而事件比事實(shí)更具有內(nèi)存效率。(但是,請(qǐng)注意,您必須在 Drools-Spring 配置中將事件處理模式設(shè)置為 STREAM;否則滑動(dòng)窗口之類的時(shí)態(tài)操作符會(huì)停止工作。)

      使用已聲明的類型

      清單 6 中需要注意的其他一些事項(xiàng)包括:MemberCase 事實(shí)(不屬于事件類型)也針對(duì)事件約束進(jìn)行了評(píng)估,就像我們只評(píng)估之間 30 多天內(nèi)創(chuàng)建的案例一樣。某個(gè)案例可能今天已經(jīng)存在 29 天了,但明天就是 30 天了,這就意味著必須在每一天的一開始就對(duì) Case Supervision 規(guī)則進(jìn)行重新評(píng)估。不幸的是,Drools 并不提供 “今天” 變化的量。因此,作為一個(gè)變通方案,我添加了一個(gè)名為 Today 的事件類型;這是一個(gè) Drools 聲明類型,或是一個(gè)用規(guī)則語(yǔ)言(而不是用 Java 代碼)聲明的數(shù)據(jù)構(gòu)造。

      這種特殊的事件類型根本不聲明任何顯式屬性,除了一個(gè)隱式的 @timestamp 元數(shù)據(jù),后者是在 Today 事件斷言到工作內(nèi)存中時(shí)進(jìn)行自動(dòng)填充。另一個(gè)元數(shù)據(jù) @expires(24h) 指定,某個(gè) Today 事件會(huì)在斷言后的 24 小時(shí)內(nèi)到期。

      要想在每天的一開始重設(shè) Today,還需要在 Set Today 規(guī)則之上添加了一個(gè) timer。先激活這個(gè)規(guī)則,然后就會(huì)在每天的一開始觸發(fā)該規(guī)則來(lái)插入一個(gè)新鮮的 Today 事件,該事件將取代剛剛期滿的事件。隨后,新鮮的 Today 事件會(huì)觸發(fā) Case Supervision 規(guī)則的重估。還要注意的是,如果規(guī)則的條件中沒有出現(xiàn)事實(shí)更改,那么計(jì)時(shí)器本身無(wú)法觸發(fā)規(guī)則的重估。計(jì)時(shí)器也不能重新評(píng)估函數(shù)或內(nèi)聯(lián)的 eval,因?yàn)?Drools 將這些構(gòu)造函數(shù)的返回結(jié)果作為時(shí)間常量,并緩存它們的值。

      何時(shí)使用事實(shí)與事件的比較

      了解事實(shí)和事件之間的區(qū)別有助于我們輕松決定何時(shí)使用每種類型:

      • 在某個(gè)時(shí)間點(diǎn)或某段持續(xù)時(shí)間內(nèi),當(dāng)數(shù)據(jù)表示系統(tǒng)狀態(tài)的不可變快照時(shí),應(yīng)該將事件 用于場(chǎng)景,該事件是時(shí)間敏感的,并且會(huì)很快到期,或可以預(yù)計(jì)數(shù)據(jù)量會(huì)快速且持續(xù)增長(zhǎng)。
      • 在數(shù)據(jù)對(duì)業(yè)務(wù)域至關(guān)重要的地方,以及數(shù)據(jù)將體驗(yàn)正在進(jìn)行的更改并且這些更改要求持續(xù)重估規(guī)則的時(shí)候,應(yīng)該將事實(shí) 用于場(chǎng)景。

      Drools 查詢

      下一個(gè)步驟是提取規(guī)則執(zhí)行結(jié)果,這通過(guò)查詢工作內(nèi)存中的事實(shí)來(lái)完成。(一種替代方法是通過(guò)調(diào)用規(guī)則語(yǔ)法右手邊的 global 上的方法,讓規(guī)則引擎將結(jié)果傳遞給應(yīng)用程序。)在這個(gè)例子中,事實(shí)斷言和規(guī)則觸發(fā)都立即發(fā)生,沒有任何延遲,這就確保了我們?cè)?清單 6 中的查詢會(huì)返回實(shí)時(shí)的報(bào)告。因?yàn)?TransientReminder 事實(shí)是通過(guò)邏輯方式斷言的,所以在它們的條件再無(wú)法得到滿足時(shí),規(guī)則引擎會(huì)自動(dòng)從工作內(nèi)存中收回它們。

      可以說(shuō)提醒 是在早上由一個(gè)規(guī)則引擎在特定的案例上生成的。隨后,我們?cè)?Java 代碼中執(zhí)行查詢 “CaseReminderQuery”,如 清單 3 所示,因此會(huì)返回一個(gè)提醒,并向系統(tǒng)中的所有臨床醫(yī)生顯示該提醒。如果在下午某個(gè)臨床醫(yī)生完成了案例上的一個(gè)評(píng)估,并生成了一個(gè)新的案例監(jiān)督記錄,該事件會(huì)打破用于提醒 事實(shí)的條件。Drools 隨后會(huì)自動(dòng)收回它。我們要確定該提醒事實(shí)消失了,方法是在完成案例評(píng)估之后立即運(yùn)行相同的查詢。邏輯斷言可以使推理結(jié)果保持最新,而且規(guī)則引擎運(yùn)行在內(nèi)存效率模式之下,這與事件的行為非常相像。

      邏輯事實(shí)計(jì)數(shù)器

      請(qǐng)注意,每一個(gè)以邏輯方式斷言的事實(shí)都附帶一個(gè)計(jì)數(shù)器,該計(jì)數(shù)器在每次斷言一個(gè)同等 事實(shí)時(shí)都增加。如果不再保持反復(fù)斷言同等 事實(shí)的眾多規(guī)則中的某個(gè)規(guī)則,那么用于邏輯事實(shí)的計(jì)數(shù)器就會(huì)減少。當(dāng)計(jì)數(shù)器達(dá)到 0 時(shí),就會(huì)自動(dòng)收回該事實(shí)。

      現(xiàn)場(chǎng)查詢 更是錦上添花。讓一個(gè)現(xiàn)場(chǎng)查詢處于打開狀態(tài),這會(huì)創(chuàng)建一個(gè)查詢結(jié)果視圖,并發(fā)布給定視圖內(nèi)容的更改事件。這意味著現(xiàn)場(chǎng)查詢恰好需要運(yùn)行一次,由此產(chǎn)生的結(jié)果視圖會(huì)使用由規(guī)則引擎發(fā)布的正在進(jìn)行的更改來(lái)實(shí)現(xiàn)自動(dòng)更新。

      到目前為止,您可能已經(jīng)發(fā)現(xiàn),只需一點(diǎn)點(diǎn)有關(guān) Drools、JPA 和 Spring 的背景知識(shí),就可以輕松實(shí)現(xiàn)一個(gè)持續(xù)的實(shí)時(shí)數(shù)據(jù)分析應(yīng)用程序。我們將通過(guò)一些將改進(jìn)我們的案例管理解決方案的高級(jí)編程步驟來(lái)結(jié)束本文。

      高級(jí) Drools 編程

      管理關(guān)系

      FactHandle 的一個(gè)有趣的約束條件是它只與當(dāng)前事實(shí)相關(guān)聯(lián),與事實(shí)的嵌套關(guān)系沒有關(guān)聯(lián)。Drools 會(huì)通過(guò)其在 getKsession().update(factHandle, memberCase) 中的 FactHandle,了解對(duì) MemberCaseid(盡管這永遠(yuǎn)不可能發(fā)生,因?yàn)橹麈I是不可變的)、startDtmendDtm 所做的更改。然而,在調(diào)用同一個(gè)方法時(shí),不會(huì)通知 Drools 有關(guān)對(duì) membercaseSupervisions 屬性所做的更改。

      同樣,系統(tǒng)不會(huì)通知 JPA 中的 EntityListener 通知有關(guān)一對(duì)多和多對(duì)多關(guān)系的更改。這是因?yàn)橥怄I位于相關(guān) 表或鏈接 表中。

      為了根據(jù)更新的事實(shí)與這些關(guān)系建立連接,我們可以構(gòu)建遞歸邏輯,獲取每個(gè)嵌套關(guān)系的 FactHandle。一個(gè)更好的解決方案是將 EntityListener 放置在與規(guī)則條件有關(guān)中的所有實(shí)體上(包括鏈接表)。我們使用 MemberCaseSupervision 來(lái)完成此操作,其中更改由每個(gè)實(shí)體自己的 EntityListenerFactHandle 來(lái)處理的(參見 清單 2清單 3)。

      規(guī)則評(píng)估期間的實(shí)體懶惰式加載

      除非我們已經(jīng)指定一個(gè)知識(shí)庫(kù)分區(qū)(也就是說(shuō),可以執(zhí)行并行處理),否則不會(huì)在調(diào)用 ksession.insert()、ksession.update()ksession.retract() 的同一個(gè)線程中對(duì)規(guī)則進(jìn)行評(píng)估。清單 2清單 3 中的事實(shí)斷言都發(fā)生在事務(wù)上下文中,在該上下文中,事務(wù)范圍的 JPA 持久上下文(Hibernate 會(huì)話)是可用的。這就允許規(guī)則引擎跨懶惰式加載實(shí)體關(guān)系進(jìn)行評(píng)估。如果啟用了一個(gè)知識(shí)庫(kù)分區(qū),則必須將實(shí)體關(guān)系配置為急切加載,以避免產(chǎn)生 JPA LazyInitializationException

      啟用事務(wù)

      默認(rèn)情況下,Drools 不支持事務(wù),因?yàn)樗诠ぷ鲀?nèi)存中不保存任何歷史快照。這對(duì)我們的 EntityListener 來(lái)說(shuō)是一個(gè)問(wèn)題,因?yàn)樯芷诨卣{(diào)方法是在數(shù)據(jù)庫(kù)刷新之后、但在事務(wù)提交之前調(diào)用的。如果事務(wù)被回滾又會(huì)怎么樣呢?如果那樣的話,JPA 持久上下文中的實(shí)體將變?yōu)榉蛛x的實(shí)體,且與數(shù)據(jù)庫(kù)表中的行不一致,而且工作內(nèi)存中的行也是如此。規(guī)則引擎推理結(jié)果將不再是可信的。

      啟用事務(wù)通過(guò)確保工作內(nèi)存中的數(shù)據(jù)庫(kù)和應(yīng)用程序數(shù)據(jù)庫(kù)始終同步,且規(guī)則推理結(jié)果始終準(zhǔn)確,使我們的案例管理系統(tǒng)具有防彈功能。在 Drools 中,適當(dāng)應(yīng)用 JPA 和 JTA 實(shí)現(xiàn)以及類路徑中的一個(gè) “drools-jpa-persistence” 包,可以配置一個(gè) JPAKnowledgeService(參閱 參考資料)來(lái)創(chuàng)建我們的有狀態(tài)知識(shí)會(huì)話。具有流程實(shí)例、變量和事實(shí)對(duì)象的整個(gè)有狀態(tài)知識(shí)會(huì)話可以映射為表 “SessionInfoThe”(將 ksessionId 作為主鍵)中行的一個(gè)二進(jìn)制列。

      當(dāng) 我們通過(guò)注釋或 XML 在應(yīng)用程序中指定事務(wù)邊界時(shí),應(yīng)用程序啟動(dòng)的事務(wù)會(huì)傳播到規(guī)則引擎。無(wú)論什么時(shí)候發(fā)生事務(wù)回滾,有狀態(tài)知識(shí)會(huì)話都會(huì)恢復(fù)到數(shù)據(jù)庫(kù)中保存的以前的狀態(tài)。這維 護(hù)了應(yīng)用程序數(shù)據(jù)庫(kù)和 Drools 數(shù)據(jù)庫(kù)之間的一致性和集成。當(dāng)同時(shí)從多個(gè) JTA 事務(wù)中進(jìn)行訪問(wèn)時(shí),內(nèi)存中的單個(gè)有狀態(tài)知識(shí)會(huì)話應(yīng)當(dāng)像 REPEATABLE READ 一樣運(yùn)轉(zhuǎn);否則,單個(gè) SessionInfo 實(shí)體實(shí)例可能具有一些從不同事務(wù)所做的混合狀態(tài)更改,這會(huì)打破事務(wù)劃分。請(qǐng)注意,自編寫 REPEATABLE READ 起,就不能確定它是否能夠通過(guò) drools-jpa-persistence 包的事務(wù)管理器來(lái)實(shí)現(xiàn)。

      集群

      如果應(yīng)用程序要在集群環(huán)境下運(yùn)行,前面描述的方法很快就會(huì)失敗。每個(gè)嵌入式規(guī)則引擎的實(shí)例都會(huì)接收同一個(gè)節(jié)點(diǎn)上發(fā)生的實(shí)體事件,這會(huì)導(dǎo)致不同節(jié)點(diǎn)上的工作內(nèi)存不同步。我們可以使用一個(gè)通用的遠(yuǎn)程 Drools 服務(wù)器(參閱 參考資料)來(lái)解決這個(gè)問(wèn)題。不同節(jié)點(diǎn)上的實(shí)體實(shí)例會(huì)通過(guò) REST/SOAP Web 服務(wù)通信向集中式 Drools 服務(wù)器發(fā)布其所有的事件。隨后應(yīng)用程序可以從 Drools 服務(wù)器訂閱推理結(jié)果。請(qǐng)注意,Drools 服務(wù)器中 SOAP 的 Apache CXF 實(shí)現(xiàn)目前不支持 ws-transaction。考慮到針對(duì)該真實(shí)用例概述的義務(wù)性事務(wù)需求,我希望很快就能提供這方面的支持。

      結(jié)束語(yǔ)

      在本文中,您有機(jī)會(huì)匯總一些您已經(jīng)了解的有關(guān)在 Spring 和 JPA 中進(jìn)行 POJO 編程的知識(shí),同時(shí)本文還匯總了一些在 Drools 5 中可用的新特性。我已經(jīng)演示了如何巧妙使用 EntityListener(一個(gè)全局 Drools 會(huì)話)和 fireUtilHalt() 方法,用它們來(lái)開發(fā)一個(gè)基于 POJO 的持續(xù)實(shí)時(shí)數(shù)據(jù)分析應(yīng)用程序。您已經(jīng)了解了核心的 Drools 概念,比如致力于 “事實(shí)與事件的比較” 之類的主題,還了解了如何編寫邏輯斷言,以及更高級(jí)的主題和使用,比如,事務(wù)管理和將 Drools 實(shí)現(xiàn)擴(kuò)展到一個(gè)集群環(huán)境中。請(qǐng)參閱 應(yīng)用程序源代碼,了解有關(guān) Drools 5 的更多信息。

      下載

      描述名字大小
      本文的樣例代碼j-drools5-src.zip5KB

      參考資料

      學(xué)習(xí)

      獲得產(chǎn)品和技術(shù)

      • 下載 Drools 5:本文中使用了 Drools Expert 和 Drools Fusion,它們可以實(shí)現(xiàn)規(guī)則引擎和 CEP 框架。

      討論

      • 加入 developerWorks 中文社區(qū)。查看開發(fā)人員推動(dòng)的博客、論壇、討論組和維基,并與其他 developerWorks 用戶交流。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多