负责管理复杂工作流,业务规则和商业智能的企业开发人员可以Swift实现集成了工作流引擎,企业服务总线(ESB)和规则引擎的企业平台的价值。 到目前为止,这个最佳点已经被商业产品所填补,例如IBMWebSphere®Process Server / WebSphere Enterprise Service Bus(请参阅参考资料 )和Oracle SOA Suite。 来自JBoss社区的Drools 5是一个开源替代方案,它通过一组统一的API和一个共享的有状态知识会话无缝地集成了jBPM工作流引擎和规则引擎。
Drools 5业务逻辑集成平台主要由Drools Expert和Drools Fusion组成 ,它们共同构成了平台的规则引擎和用于复杂事件处理/时间推理的基础架构。 本文的示例应用程序是基于这些核心功能构建的。 请参阅相关主题 ,以了解更多关于在Drools中5可用的附加软件包。
普通的旧Java对象(POJO)首先是在Spring框架中实现的。 POJO以及依赖注入(DI)和面向方面的编程(AOP)标志着对简单性的回归,有效地将Spring提升为开发Web应用程序的行业标准。 POJO的采用从Spring到EJB 3.0和JPA,再到XML到Java的绑定技术,例如JAXB和XStream。 最近,POJO通过Hibernate Search集成到了全文搜索引擎Lucene中(请参阅参考资料 )。
如今,由于这些不断发展的进步,应用程序的POJO数据模型可以跨多个层传播,并可以直接通过网页或SOAP / REST Web服务端点公开。 作为一种编程模型,POJO既具有成本效益,又具有非侵入性,可在简化企业体系结构的同时为开发人员节省时间。
现在,Drools 5通过允许程序员将POJO作为事实直接插入到知识会话或规则引擎所说的“工作内存”中,将POJO编程的简单性提高到了新的水平。 本文介绍了一种经济高效且非侵入式的方法,该方法将Dr.L工作实体中的JPA实体作为事实进行处理。 连续,实时的数据分析从未如此简单。
许多医疗保健提供者使用病例管理系统作为一种经济有效的方式来跟踪医疗记录,例如护理,处方和评估。 基于这样一个系统的示例程序具有以下流程和要求:
为此用例选择业务流程管理(BPM)工作流和规则引擎是有意义的:系统使用数据概要分析/分析规则(在上面的列表中以斜体显示),每种情况都可以视为jBPM中长期运行的流程/工作流。 ,我们可以使用Drools Planner来满足自动调度的要求。 出于本文的目的,我们仅关注程序的业务规则。 还要说,系统要求在满足规则条件时实时实时生成提醒和通知。 因此,这是一个连续的实时数据分析用例。
清单1显示了在我们的系统中声明的三个实体类: MemberCase
, Clinician
和CaseSupervision
:
@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
实例代表一个患者案例。 Clinician
代表该设施中的临床医生。 CaseSupervision
临床医生进行病例评估时,都会产生一个CaseSupervision
记录。 这三个实体一起是要定义的业务规则中的事实类型 。 另请注意,上述CaseSupervision
在Drools中被声明为事件类型 。
从应用程序的角度来看,我们可以在系统中的任何位置,不同的屏幕和不同的工作流程中修改这三种类型的实体。 我们甚至可以使用Spring Batch之类的工具来批量更新实体。 但是,为了这个示例的缘故,让我们假设我们将仅通过JPA持久性上下文来更新实体。
请注意,该示例应用程序是使用Maven进行构建的Spring-Drools集成。 我们将在本文后面查看一些配置详细信息,但是您可以随时下载源zip 。 现在,让我们考虑使用Drools 5的一些概念性功能。
规则引擎的一般概念是事实是规则推理的数据对象。 在Drools中,事实是您从应用程序中获取并断言到引擎工作内存中的任意Java Bean。 或者,正如JBoss Drools 参考手册中所写:
规则引擎根本不会“克隆”事实,而是一天结束时所有的引用/指针。 事实就是您的应用程序数据。 没有getter和setter的字符串和其他类不是有效事实,并且不能与依赖于getter和setter的JavaBean标准与对象进行交互的Field Constraints一起使用。
除非您在规则之上指定了关键字no-loop
或lock-on-active
,否则只要工作内存中发生事实更改,Drools规则引擎中的规则都会被重新评估。 您还可以使用@PropertyReactive
和@watch
批注指定Drools应该监视的事实属性。 流口水将忽略该事实的所有其他属性的更新。
为了进行真正的维护,有三种方法可以安全地更新Drools工作内存中的事实 :
modify
块内进行更新。 由于激活规则而更改事实时,请使用此方法。 FactHandle
; 用于由应用程序Java代码进行的事实更改。 Fact
类实现PropertyChangeSupport
通过JavaBeans规范定义; 使用它来将Drools注册为Fact
对象的PropertyChangeListener
。 作为安静的观察者 ,我们的规则不会更新Drools工作记忆中的任何JPA实体事实。 相反,它们将生成逻辑事实作为推理结果。 (请参见下面的清单6。 )但是,在规则中更新JPA实体需要格外注意,因为更新的实体可能处于分离状态,或者可能没有事务或只读事务与当前线程相关联。 结果,对实体所做的更改将不会保存到数据库。
尽管事实对象是按引用传递的,但Drools(与JPA / Hibernate不同)无法跟踪规则以外的事实更改。 通过使用FactHandle
通知Drools有关在应用程序Java代码中进行的事实更改,可以避免不一致的规则推理结果。 然后,Drools将适当地重新评估规则。 FactHandle
是表示您在工作内存中声明的事实对象的令牌。 这是您希望修改或撤回事实时通常与工作内存进行交互的方式。 在示例应用程序( 清单2和清单3 )中,我们使用FactHandle
操作工作内存中的实体事实。
您可以通过实现PropertyChangeSupport
(捕获对bean的属性所做的每个更改)来解决Drools无法跟踪事实更改的问题。 但是请记住,然后您必须解决频繁重新评估规则对性能的影响。
您可以通过POJO事实将JPA实体作为域数据对象插入Drools的工作内存中。 这样做可以避免为Value Object / DTO层以及JPA实体和DTO之间的相应转换层进行数据建模。
虽然将实体用作事实将简化您的应用程序代码,但您必须特别注意实体生命周期阶段。 实体事实应以托管(永久)或分离状态保存。 永远不要将瞬态实体插入Drools工作内存中,因为它们尚未保存在数据库中。 同样,您应该从工作内存中撤回已删除的实体。 否则,您的应用程序数据库和规则引擎的工作内存将不同步。
因此,我们想到了一个价值数百万美元的问题:我们如何有效地通过FactHandle
通知规则引擎有关在应用程序代码中进行的实体更改?
如果我们试图以命令式的编程思想来应对这一挑战,那么最终insert()
在知识会话上调用相应的JPA API方法旁边的insert()
, update()
和retract()
方法。 这种方法将是Drools API的侵入式使用,并且会将意大利面条代码留在应用程序中。 更糟的是,在读/写事务结束时,JPA中的更新(脏)实体会自动与数据库同步,而无需对持久性上下文进行任何显式调用。 我们将如何拦截这些变化并通知Drools? 另一个选择是,像典型的商业智能(BI)工具一样,在单独的过程中轮询实体更改,可以使核心业务功能保持整洁,但是这将很困难,实施成本高昂,并且结果将不会即时产生。
JPA EntityListener
是一种AOP拦截器,非常适合我们的用例。 在清单2中 ,我们将定义两个EntityListener
,它们侦听对应用程序中三种类型的实体所做的所有更改。 这种方法使实体在JPA中的生命周期与在Drools中的生命周期始终保持同步。
在实体生命周期回调方法中,我们为给定的实体实例查找FactHandle
,然后根据JPA生命周期阶段通过返回的FactHandle
更新或撤回事实。 如果缺少FactHandle
,则将实体作为新事实插入到工作内存中,以进行实体更新或保留。 由于该实体在工作内存中不存在,因此在调用JPA delete时无需将其从工作内存中删除。 清单2中显示的两个JPA EntityListener
用于工作内存的两个不同的入口点或分区。 第一个入口点在MemberCase
和Clinician
之间共享,第二个入口点用于CaseSupervision
事件类型。
@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
方法使系统的核心业务逻辑保持整洁。 请注意,此方法需要将一个或多个Drools全局知识会话注入到两个EntityListener
。 在本文后面的部分,我们将知识会话声明为单例Spring bean。
启动应用程序时,三种实体类型的所有现有记录都已从数据库预加载到工作存储器中,以进行规则评估,如清单3所示。 从那时起,将通过两个EntityListener
通知工作内存所有对实体所做的更改。
@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;
}
}
}
注意,在清单3中,我们可以选择在每个EntityListener
的回调方法中调用fireAllRules()
。 我通过在渴望加载的Spring bean的“ @PostConstruct
”方法中调用一次fireUntilHalt()
方法来简化此过程。 应该将fireUtilHalt
方法在一个单独的线程中调用一次(请查看Spring的@Async
批注),然后继续触发规则激活,直到调用暂停为止。 如果没有要激活的激活, fireUtilHalt
将等待将激活添加到活动议程组或规则流组中。
我可以选择在应用程序的Spring XML配置文件(如下所示)中触发规则甚至启动流程。 但是,在尝试配置fireUntilHalt()
方法时,我检测到了可能的线程处理问题。 在规则评估期间延迟加载实体关系时,结果是“数据库连接关闭错误”( 请参阅高级主题 )。
现在,让我们花点时间看一下Spring-Drools集成的一些配置细节。 清单4是应用程序的Maven pom.xml的代码片段,其中包括Drools核心,Drools编译器和Drools Spring集成包的依赖项:
<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中 ,我将全局有状态知识会话配置为单例Spring bean。 (无状态知识会话不会在持久调用中工作,因为它不会在迭代调用期间保持其状态。) 清单5中<drools:assert-behavior mode="EQUALITY" />
一个重要设置是<drools:assert-behavior mode="EQUALITY" />
。
在JPA / Hibernate中,将受管实体与标识进行比较,而将独立实体与相等进行比较。 与单例生命周期相比,插入到有状态知识会话中的实体从JPA角度Swift脱离,因为事务作用域的持久性上下文,甚至“扩展”或“流作用域”的持久性上下文(请参阅参考资料 )都是短暂的。有状态的知识会议。 通过不同的持久性上下文对象获取的同一实体每次都是不同的Java对象。 默认情况下,Drools使用身份比较。 因此,当你仰望FactHandle
通过工作记忆对现有的实体事实ksession.getFactHandle(entity)
,最有可能的Drools不会找到一个匹配。 为了匹配分离的实体,我们必须在配置文件中选择EQUALITY
。
<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" />
有关更完整的配置详细信息,请参见应用程序源代码。
清单6定义了两个复杂的事件处理(CEP)规则。 除了作为JPA实体的两个事实类型MemberCase
和Clinician
, CaseSupervision
实体类还声明为事件。 临床医生的每个病例评估任务都会生成一个CaseSupervision
记录。 创建后,记录不太可能经历任何正在进行的更改。
清单6中规则“案件监督”的条件测试了过去30天内是否对该案件进行过案件监督。 如果不是,则规则的结果/动作部分将生成一个TransientReminder
事实(如清单7所示 ),并在逻辑上将该事实插入工作内存中。 临床医生监督规则规定,临床医生应在过去7天内至少完成一次病例监督; 如果不是,则规则的结果/操作部分会生成类似的TransientReminder
事实,该事实(同样)会在逻辑上插入到工作内存中。
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
请注意, 清单7中显示的TransientReminder
事实不是JPA实体,而是常规的POJO。
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);
}
}
事件是用时间元数据装饰的事实,例如@timestamp
, @duration
和@expires
。 事实与事件之间最重要的区别是,在Drools的上下文中事件是不可变的。 如果事件可能发生更改,则更改(称为“事件数据丰富”)不应影响规则执行的结果。 这就是为什么我们仅在@PostPersist
的EntityListener
中监视@PostPersist
实体生命周期阶段的CaseSupervision
(请参见清单2 )。
Drools对Sliding Windows协议的支持使事件对于时间推理特别强大。 滑动窗口是一种对感兴趣的事件进行范围划分的方法,就好像它们属于不断移动的窗口一样。 两种最常见的滑动窗口实现是基于时间的窗口和基于长度的窗口。
在清单6中显示的示例规则中, over window:time(30d)
建议规则引擎对最近30天创建的CaseSupervision
事件进行评估。 30天过后,不可变的事件将不再进入窗口,Drools将自动从工作内存中撤回事件,并且规则将相应地重新评估。 因为事件是不可变的,所以Drools自动管理事件的生命周期。 因此,事件比事实更具存储效率。 (但是请注意,在Drools-Spring配置中必须将事件处理模式设置为STREAM
;否则,滑动窗口之类的临时运算符将无法工作。)
清单6中需要注意的MemberCase
是, MemberCase
事实(非事件类型)也根据时间限制进行评估,因为我们仅评估30天内创建的案例。 案件今天可能是29天,明天是30天,这意味着必须在每天开始时重新评估“案件监督”规则。 可悲的是,Drools没有提供滑动的“今天”变量。 因此,作为一种解决方法,我添加了一个名为Today
的事件类型; 这是Drools 声明的type ,或者是以规则语言而不是Java代码声明的数据构造。
这种特殊事件类型根本不声明任何显式属性,除了隐式@timestamp
元数据外,该元数据在将Today
事件断言到工作内存时自动填充。 另一个元数据@expires(24h)
指定, Today
事件在声明后24小时到期。
要在每天开始时重置“ Today
”,我还在“设置今天”规则的基础上添加了一个timer
。 每天开始时都会激活并触发此规则,以插入一个新的Today
事件,该事件将替换刚刚过期的事件。 随后,新的“ Today
事件触发了“案件监督”规则的重估。 还要注意的是,如果不改变规则的条件,计时器本身就不会触发规则的重新评估。 计时器也不会重新评估函数或内联eval
,因为Drools会将这些构造的返回视为时间常数并对其值进行缓存。
了解事实与事件之间的区别有助于我们更轻松地决定何时使用每种类型:
下一步是提取规则执行结果,这是通过查询工作内存中的事实来完成的。 (另一种方法是让规则引擎通过在规则语法的右侧调用global
上的方法,将结果传递给应用程序。)在此示例中,事实断言和规则触发都在没有发生的情况下立即发生任何延迟,确保清单6中的查询将返回实时报告。 由于TransientReminder
事实在逻辑上被断言,因此规则引擎将在不再满足其条件时自动将其从工作内存中撤回。
假设今天早上规则引擎针对特定案例生成了提醒 。 随后,我们在Java代码中执行查询“ CaseReminderQuery
”,如清单3所示,以便返回提醒并将其显示给系统中的所有临床医生。 如果下午临床医生完成了对病例的评估并生成了新的病例监督记录,则此事件将打破提醒事实的条件。 然后,流口水会自动将其缩回。 我们可以通过在案例评估完成后立即运行相同的查询来确认提醒事实已消失。 逻辑断言可以使您的推理结果保持最新状态,并且规则引擎以类似于事件的内存高效模式运行。
实时查询使锦上添花。 实时查询保持打开状态,创建查询结果视图并发布给定视图内容的更改事件。 这意味着实时查询只需要运行一次,并且结果视图将自动更新,并由规则引擎发布正在进行的更改。
到目前为止,您已经看到,在Drools,JPA和Spring方面只有很少的背景知识,实现连续的实时数据概要分析应用程序并不困难。 我们将以一些高级编程步骤作为结束语,这些步骤将强化我们的案例管理解决方案。
FactHandle
一个有趣的约束是它仅与当前事实相关联,而与事实的嵌套关系无关。 Drools的将要进行的更改通知MemberCase
的id
(尽管这永远不会发生,因为主键是不可变的), startDtm
或endDtm
通过其FactHandle
在getKsession().update(factHandle, memberCase)
但是,当您调用同一方法时,不会通知有关member
和caseSupervisions
属性的更改。
同样,不会通知JPA中的EntityListener
一对多和多对多关系的更改。 这是因为外键位于相关表或链接表中。
为了将这些关系作为更新的事实进行连接,我们可以构建递归逻辑以获取每个嵌套关系的FactHandle
。 更好的解决方案是将EntityListener
放置在所有参与规则条件的实体(包括链接表)上。 我们使用Member
和CaseSupervision
做到了这CaseSupervision
,其中更改是由每个实体自己的EntityListener
和FactHandle
(请参见清单2和清单3 )。
除非我们指定了知识库分区(即并行处理),否则将在ksession.insert()
, ksession.update()
或ksession.retract()
的同一线程中评估规则。 清单2和清单3中的事实断言都在事务上下文中发生,在该事务上下文中,可以使用事务范围的JPA持久性上下文(Hibernate会话)。 这使规则引擎可以评估延迟加载的实体关系。 如果启用了知识库分区,则必须将实体关系配置为渴望加载,以防止发生JPA LazyInitializationException
。
默认情况下,Drools不支持事务,因为它不会在工作内存中保留数据的任何历史快照。 这是我们EntityListener
的问题,因为生命周期回调方法是在数据库刷新之后但在事务提交之前调用的。 如果交易被回滚怎么办? 在那种情况下,JPA持久性上下文中的实体将变得分离,并且与数据库表中的行不一致,工作内存中的事实也是如此。 规则引擎的推理结果将不再可信。
通过确保工作存储器和应用程序数据库中的数据始终保持同步并且规则推理结果始终准确,启用事务将使我们的案例管理系统更加安全。 在Drools中,用到位JPA和JTA的实现和“ drools-jpa-persistence
”包中的类路径,一个JPAKnowledgeService
(见相关信息 )可以被配置为创建有状态会话知识。 带有流程实例,变量和事实对象的整个有状态知识会话都映射为表“ SessionInfo”中一行的二进制列,其中ksessionId
为主键。
当我们通过注释或XML在应用程序代码中指定事务边界时,应用程序启动的事务将传播到规则引擎。 每当发生事务回滚时,有状态知识会话将恢复到数据库中保存的先前状态。 这样可以保持应用程序数据库和Drools数据库之间的一致性和集成性。 从多个JTA事务中同时访问时,内存中的单例有状态知识会话的行为应类似于REPEATABLE READ
; 否则,单个SessionInfo
实体实例可能具有从不同事务进行的混合状态更改,从而破坏了事务划分。 请注意 ,在撰写本文时,还不确定drools-jpa-persistence
包的事务管理器是否实现了REPEATABLE READ
。
如果我们的应用程序要在群集环境下运行,那么前面描述的方法将很快失败。 嵌入式规则引擎的每个实例将接收仅在同一节点上发生的实体事件,从而导致不同节点上的工作内存不同步。 我们可以使用通用的远程Drools服务器来解决此问题(请参阅参考资料 )。 不同节点上的实体侦听器将通过REST / SOAP Web服务通信将其所有事件发布到集中式Drools服务器,然后应用程序可以订阅Drools服务器的推理结果。 请注意 ,Drools服务器中SOAP的Apache CXF实现目前不支持ws-transaction
。 鉴于针对此实际用例概述的强制性交易要求,我希望它将很快可用。
在本文中,您有机会将有关Spring和JPA的POJO编程的一些知识与Drools 5中可用的一些新功能结合在一起。我已经演示了如何巧妙地使用EntityListener
,这是一个全球性Drools会话。以及fireUtilHalt()
方法来开发基于POJO的连续实时数据分析应用程序。 您已经学习了Drools的核心概念,例如处理事实与事件的主题以及如何编写逻辑断言,还学习了更高级的主题和用法(例如事务管理)以及将Drools实现扩展到集群环境中。 请参阅应用程序源代码以了解有关Drools 5的更多信息。
翻译自: https://www.ibm.com/developerworks/java/library/j-drools5/index.html