不能登上播出wwW3jpav3的页面访问升级啦,是维护的缘故3jpav3com照成的吗

JPA移植到PostgreSQL时关于CLOB, BLOB及JSON类型的处理
时间: 12:59:37
&&&& 阅读:579
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&一、综述
目前的项目最初基于Oracle开发,现在要移植到PostgreSQL。鉴于已经使用JPA来实现对象的持久化,领导总以为迁移任务很easy,但实际过程中还是出现了很多问题。
这其中有一些问题是定义EJB时不规范引起的,如把Number(1)映射为boolean、Number(n)映射为String、Date映射为String等等。因为Oracle拥有强大的自动类型转换能力,只要数据符合格式,Oracle不会报错;一旦移植到PostgreSQL环境,各种类型不匹配的Exception抛来抛去。不过只要按规范一一修正过来,这些问题还是容易解决的。
另外几个问题就很令人头疼,特别是关于CLOB, BLOB及JSON类型的处理。移植到PostgreSQL时,绝不是简单地将CLOB替换为TEXT、BLOB替换为BYTEA、Varchar2(...) CONSTRAINT ... CHECK (... IS JSON) 替换为JSON后,余下的交给JPA就能搞定。接下来的麻烦得自己去一一去解决。
本文即是根据搜索到的资料,加上自己操作过程中的经验,进行一些实践上的总结。
二、CLOB和BLOB的处理
对于CLOB(PostgreSQL对应的是TEXT,后文不作区分)类型,写入时不抛例外,但实际上存储的是一个数值,而不是byte[]内容;读取时,部分行正确,部分行抛例外:column xxx is of type text but expression is of type bigint ...;对于BLOB(对应的是BYTEA,不作区分)类型,干脆写入时就报错:column xxx is type of bytea but expression is type of bigint ...。
2&PostgreSQL处理LOB数据的两种方式
要解释原因,首先需知道PostgreSQL处理LOB数据的两种方式:oid + largeobject 和byte[],详细说明参阅:
参考资料1:
很明显,JPA把期望的二进制数组方式当作oid+largeobject方式传递给了PostgreSQL,于是当遇到写入CLOB或BLOB时,相应字段存入的实际上是oid的值(BigInt类型),而byte[]的值则被写入到公共的pg_largeobject表。区别在于,BigInt类型的oid自动转换到TEXT时成功了,转换到BYTEA时失败。
为验证这一说法,用已经存入TEXT字段的数值去pg_largeobject查询,确实是期望的byte[]的值,这也是JPA读取时有些能成功的原因;至于不成功的那些记录,猜测可能与字节数有关,因为字节数超过1M的都成功而在K级别的都失败(临界值未知)。鉴于篇幅,这些内容不展开,有兴趣者请自行验证。
oid + largeobject&方式除了性能上有些优势外,至少有三个缺点:1 公用的pg_largeobject存在权限问题;2 pg_largeobject的相应记录不会随源记录删除而自动删除;3 对事务有较严格限制。因此并不符合项目要求,但为什么JPA总是按oid + largeobject方式来处理?
3 Hibernate与PostgreSQL的不统一之处
以BYTEA为例,PostgreSQL的两种处理方式是通过分别调用JDBC的setBinaryStream()和setBlob()接口来实现的。期望的逻辑应该是Hibernate能针对PostgreSQL的这个特点来正确区分、正确调用,但不幸的是:Hibernate以为所有数据库都是调用setBinaryStream()来写入BYTEA,出于某种原因并不打算照顾PostgreSQL的特殊情况(貌似一段时间内不会改观),于是前面提到的错误现象发生了。
详细的解释请参阅:
参考资料2:
&至于TEXT,情况大致类似,只是调用的是另外两个JDBC接口,不再展开。
4 解决办法
还是在参考资料2,提出两种解决针对BYTEA的解决思路:
在定义EJB时,将blob类型改为byte[];
重载Hibernate中的PostgresDialect类的useInputStreamToInsertBlob()方法。
经实际测试,两种思路均不甚成功,可能是与版本差异和环境差异有关,还需要修改一些其它因素才行。未继续深入研究,部分原因是因为时间紧迫,部分原因是在解决TEXT时顺带解决了(见下)。
对于TEXT,资料3提出三种解决思路:
参考资料3:
定义EJB时,取消@Lob标注,按String对待;
定义EJB时,保留@Lob标注,增加&@Type(type = "org.hibernate.type.TextType")标注;
不修改EJB,重载PostgresDialect类remapSqlTypeDescriptor()方法,将CLOB当longvarchar处理。
经实际测试,三种思路均可达到目的。由于上级领导不赞成修改标注的方式(理由是与Oracle环境的版本不一致),遂采用思路3,顺带着将BLOB按longVarBinary处理。
因项目中PostgreSQL是9.4版,故选择从PostgreSQL94Dialect继承,一般情况下可选择PostgreSQL9Dialect。代码为:
package com.xxx.
import java.sql.T
import org.hibernate.dialect.PostgreSQL94D
import org.hibernate.type.descriptor.sql.LongVarbinaryTypeD
import org.hibernate.type.descriptor.sql.LongVarcharTypeD
import org.hibernate.type.descriptor.sql.SqlTypeD
public class PgDialect extends PostgreSQL94Dialect
public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor)
switch (sqlTypeDescriptor.getSqlType())
case Types.CLOB:
return LongVarcharTypeDescriptor.INSTANCE;
case Types.BLOB:
return LongVarbinaryTypeDescriptor.INSTANCE;
return super.remapSqlTypeDescriptor(sqlTypeDescriptor);
然后在persistence.xml中用这个类(com.xxx.pgdialect.PgDialect)替换PostgreSQL94Dialect即可。
&三、JSON的处理
&JSON类型的特殊之处在于:首先Oracle实际上是按特殊的varchar2或clob来对待;其次是Hibernate及JDBC都没有定义json类型。因此,在遇到“column xxx is type of json but expression is type of character varying ...”例外时,不能简单地照搬前述方法。
经Google,发现一篇很有价值的资料:
参考资料4:
其中给出了很多种解决思路,现简单总结如下:
定义PostgreSql表结构时,将JSON改为TEXT,即仿照Oracle的做法;
扩展Hibernate中的Type,增加关于json的自定义类型,同时增加(或重载)处理JSON的相应方法;
更换JDBC驱动为pgjdbc-ng,它提供了可以处理JSON与TEXT转换的@Conveter标注;
在PostgreSql数据库,创建隐式或显式的类型转换方法或函数,使得PostgreSQL接受JSON与TEXT的自动转换。
思路1需要应用程序保证数据符合json规范,风险较大,被否决。思路2有很多种具体实现方式(有兴趣者自行钻研),但其共同点都是需要修改EJB标注,被领导否决。思路3过于依赖某一产品,且跟2一样也要修改标注,也被否决。只剩下思路4,而事实上它也确实是最简便的方式。
在psql命令行,简单创建TEXT与JSON、Varchar与JSON互相转换的四个CAST即可:
CREATE CAST (text AS json)
WITH INOUT
AS ASSIGNMENT;
CREATE CAST (json AS text)
WITH INOUT
AS ASSIGNMENT;
CREATE CAST (varchar AS json)
WITH INOUT
AS ASSIGNMENT;
CREATE CAST (json AS varchar)
WITH INOUT
AS ASSIGNMENT;
执行之后,再无“column xxx is type of json but expression is type of character varying ...”例外。
进一步猜测,XML类型也可以按类似方法来处理。
定义EJB时一定要规范,可以避免大多数简单的类型不匹配错误;
对于CLOB和BLOB,把它们按LongVarchar和LongVarBinary处理;
对于JSON,增加隐式或显式的类型转换方法。
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文地址:http://www.cnblogs.com/wggj/p/7809832.html
&&国之画&&&& &&&&chrome插件&&
版权所有 京ICP备号-2
迷上了代码!最现代的应用程序使用关系型数据库来存储数据。最近,许多厂商改用对象数据库,以减少其对数据的维护负担。这意味着对象数据库或对象关系技术正在存储,检索,更新和维护数据的照顾。这个对象关系型技术的核心部分是映射orm.xml中的文件。随着XML不需要编译,可以很容易地进行修改多个数据源较少的管理。
对象关系映射
对象关系映射(ORM)简要地告诉什么是ORM以及它是如何工作。 ORM是从对象类型的数据隐蔽到关系型,反之亦然编程能力。
ORM主要特征是映射或绑定一个目的是它的数据库中的数据。而映射,我们要考虑的任何其他表中的数据,数据的类型,并具有自一个或多个实体的关系。
惯用的持久性:它使您能够编写使用面向对象的类持久性类。
高性能:它有许多抓取技术和充满希望的锁定技术。
可靠的:它是高度稳定的,被很多专业程序员。
在ORM架构如下所示。
在上述体系结构解释了如何对象数据存储到关系数据库中的三个阶段。
第一阶段,命名为对象数据阶段,包括POJO类,服务接口和类。它是主要的业务组件层,其具有业务逻辑操作和属性。
例如,让我们举个员工数据库的架构。
Employee POJO类包含属性,如ID,姓名,工资和标识。它也包含类似属性setter和getter方法。
Employee DAO/服务类包含服务方法,如建立员工,发现员工和删除员工。
第二阶段,称为映射或持久性的阶段,包括JPA提供者,映射文件(orm.xml),JPA装载器和对象网格。
JPA提供者:这是一个包含了JPA(javax.persistence)供应的产品。例如EclipseLink,Toplink,Hibernate等。
映射文件:映射文件(orm.xml中)包含在关系数据库中的一个POJO类的数据和数据之间的映射配置。
JPA装载器:在JPA加载器的工作原理就像一个高速缓冲存储器。它可以加载关系网格数据。它的工作原理类似数据库的副本与服务类POJO数据(POJO类的属性)进行交互。
对象网格:它是可存储的关系数据的副本,如高速缓冲存储器的临时位置。对数据库的所有查询首先被实现在对象网格的数据。只有提交它才会影响到主数据库。
第三阶段是关系数据相关。它包含在逻辑上连接到所述业务组件的关系数据。如上所讨论的,仅当业务组件提交该数据,它被存储到数据库中的物理。在此之前,已修改的数据被存储在高速缓冲存储器作为一个网格格式。在获取数据的过程和存储数据是相同的。
上述三个阶段的编程交互的机制被称为对象关系映射。
Mapping.xml
mapping.xml文件指示JPA的供应者来映射实体类与数据库表。
让我们以Employee实体包含四个属性的一个例子。POJO类Employee实体的命名为:Employee.java,如下:
public class Employee
public Employee(int eid, String ename, double salary, String deg)
this.eid =
this.ename =
this.salary =
this.deg =
public Employee( )
public int getEid( )
public void setEid(int eid)
this.eid =
public String getEname( )
public void setEname(String ename)
this.ename =
public double getSalary( )
public void setSalary(double salary)
this.salary =
public String getDeg( )
public void setDeg(String deg)
this.deg =
上面的代码是Employee实体POJO类。它包含四个属性eid, ename,salary, 和 deg。考虑这些属性为表的字段,并且eid作为该表的主键。现在,我们要设计Hibernate映射文件了。映射文件名为 mapping.xml 如下:
&? xml version=&1.0& encoding=&UTF-8& ?&
&entity-mappings xmlns=&http://java.sun.com/xml/ns/persistence/orm&
xmlns:xsi=&http://www.w3.org/2001/XMLSchema-instance&
xsi:schemaLocation=&http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd&
version=&1.0&&
&description& XML Mapping file&/description&
&entity class=&Employee&&
&table name=&EMPLOYEETABLE&/&
&attributes&
&id name=&eid&&
&generated-value strategy=&TABLE&/&
&basic name=&ename&&
&column name=&EMP_NAME& length=&100&/&
&basic name=&salary&&
&basic name=&deg&&
&/attributes&
&/entity-mappings&
上述脚本用于与数据库表的映射实体类。在该文件中
&entity-mappings&&: 标签定义的模式定义,允许实体标记为XML文件。
&description&&: 标签提供了有关应用程序的描述。
&entity&&: 标签定义要转换成数据库表中的实体类。属性类定义了POJO实体类的名称。
&table&&: 标签定义的表名。如果想有两个类相同的名称以及该表中,则该标签是没有必要的。
&attributes&&: 标签定义的属性(在表中的字段)。
&id&&:&标记定义表中的主键。在&generated-value&标记定义了如何将主键值赋值,如Automatic,&Manual或者使用&Sequence。
&basic&&: 标签用于定义其余属性在表中。
&column-name&&: 标签被用来在表中定义用户定义表的字段名。
一般的XML文件用于配置特定的组件,或者映射两种不同规格的组件。在我们的例子中,我们要分别保持在一个框架的XML文件。这意味着在写一个映射的XML文件,我们需要比较用mapping.xml文件实体标签的POJO类的属性。
这里是解决方案。在类定义中,我们可以使用注释写配置的一部分。注解用于类,属性和方法。注释以'@'符号在类,属性或方法的注释中声明之前。 JPA的所有批注在javax.persistence包定义。
在这里,在我们的实例中使用的注释列表如下。
声明类为实体或表。
声明表名。
指定非约束明确的各个字段。
指定类或它的值是一个可嵌入的类的实例的实体的属性。
指定的类的属性,用于识别(一个表中的主键)。
@GeneratedValue
指定如何标识属性可以被初始化,例如自动,手动,或从序列表中获得的值。
@Transient
指定的属性,它是不持久的,即,该值永远不会存储在数据库中。
指定持久属性栏属性。
@SequenceGenerator
指定在@GeneratedValue注解中指定的属性的值。它创建了一个序列。
@TableGenerator
指定在@GeneratedValue批注指定属性的值发生器。它创造了的值生成的表。
@AccessType
这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),然后进入FIELD明智的。如果设置@AccessType(PROPERTY),然后进入属性发生明智的。
@JoinColumn
指定一个实体组织或实体的集合。这是用在多对一和一对多关联。
@UniqueConstraint
指定的字段和用于主要或辅助表的唯一约束。
@ColumnResult
参考使用select子句的SQL查询中的列名。
@ManyToMany
定义了连接表之间的多对多一对多的关系。
@ManyToOne
定义了连接表之间的多对一的关系。
@OneToMany
定义了连接表之间存在一个一对多的关系。
定义了连接表之间有一个一对一的关系。
@NamedQueries
指定命名查询的列表。
@NamedQuery
指定使用静态名称的查询。
Java Bean标准
Java类封装了实例的值及其行为为对象称为一个单元。 Java Bean是一个临时的存储和可重用的组件或对象。它是有一个默认的构造函数和getter和setter方法来初始化实例序列化的类单独的属性。
bean包含其默认构造函数或包含序列化实例的文件。因此,一个bean可以实例化另一个bean。
bean属性可以被隔离成布尔属性或者非布尔属性。
非布尔属性包含getter和setter方法。
布尔属性包含setter和方法。
任何字段的getter方法应从小字母get(Java方法的公约)开始,之后使用大写字母开头的字段名。例如,字段名为salary,因此这一字段的getter方法为getSalary()。
任何属性的setter方法应该先从小字母的集合(Java方法公约)开始,继续以大写字母,参数值设置为字段开头的字段名。例如,字段名为salary,因此这一字段的setter方法是setSalary(double sal )。
对于布尔型属性,方法是检查它是否是 true 或 false。例如,Boolean属性为空,则该字段的就是方法isEmpty()。
易百教程移动端:请扫描本页面底部(右侧)二维码并关注微信公众号,回复:"教程" 选择相关教程阅读或直接访问:http://m.yiibai.com 。
上一篇:下一篇:
加QQ群啦,易百教程官方技术学习群
注意:建议每个人选自己的技术方向加群,同一个QQ最多限加3个群。
Java技术群:
(人数:2000,等级:LV5,免费:否)
MySQL/SQL群:
(人数:2000,等级:LV5,免费:否)
大数据开发群:
(人数:2000,等级:LV5,免费:否)
Python技术群:
(人数:2000,等级:LV5,免费:否)
人工智能深度学习:
(人数:2000,等级:LV5,免费:否)
测试工程师(新群):
(人数:1000,等级:LV1,免费:是)
前端技术群(新群):
(人数:1000,等级:LV1,免费:是)
C/C++技术(新群):
(人数:1000,等级:LV1,免费:是)
Node.js技术(新群):
(人数:1000,等级:LV1,免费:是)
PostgreSQL数据库(新群):
(人数:1000,等级:LV1,免费:否)
Linux技术:
(人数:2000,等级:LV5,免费:否)
PHP开发者:
(人数:2000,等级:LV5,免费:是)
Oracle数据库:
(人数:2000,等级:LV5,免费:是)
C#/ASP.Net开发者:
(人数:2000,等级:LV5,免费:是)
数据分析师:
(人数:1000,等级:LV1,免费:是)R语言,Matlab语言等技术使用 Drools 和 JPA 实现持续的实时数据分析
企业开发人员按照管理复杂工作流、业务规则和业务智能来分派任务,这样可以快速实现企业平台的价值,该平台集成了工作流引擎、企业服务总线 (ESB) 和规则引擎。迄今为止,这个出色的平台已经被 IBM WebSphere® Process Server/WebSphere Enterprise Service Bus(参见 )和 Oracle SOA Suite 之类的商用产品填满了。来自 Boss Community 的 Drools 5 是一种开源替代方案,它通过一组统一的 API 和一个共享的、有状态的知识会话来无缝集成 jBPM 工作流引擎和规则引擎。Drools 5 的 Business Logic 集成平台主要包括 ,这两项共同组成了平台的规则引擎和用于复杂事件处理/时态推理的基础架构。本文的样例应用程序是根据这些核心特性构建的。请参阅 ,了解有关 Drools 5 中其他可用程序包的更多信息。Drools 5 中的 POJO传统的 Java 对象 (POJO) 是在 Spring 框架中首次以引人注目的方式实现的。POJO 以及依赖注入 (DI) 和面向方面的编程 (AOP) 共同标志着向简单性的回归,这种简单性有效地促进 Spring 成为开发 Web 应用程序的一种行业标准。POJO 的采用已经从 Spring 流向 EJB 3.0 和 JPA,然后再流向 XML-to-Java 绑定技术(比如 JAXB 和 XStream)。最近,POJO 已通过 Hibernate Search 集成到了全文搜索引擎 Lucene 中(参阅 )。如今,由于这些增加的改进,应用程序的 POJO 数据模型可以在多个层上进行传播,并直接通过 Web 页面或 SOAP/REST Web 服务端点进行公开。作为一种编程模型,POJO 既经济高效又属于非侵入性的,这为开发人员在简化企业架构时节约了不少时间。现在,Drools 5 通过允许直接将 POJO 作为事实 (fact) 直接插入知识会话(knowledge session)中,或是插入一个称为 “工作内存” 的规则引擎中,将 POJO 编程简单性应用于下一个级别。本文介绍了一种既经济高效又属于非侵入性的方法,这种方法将 JPA 实体作为 Drools 工作内存中的事实来进行操作。持续的实时数据分析从来不会这么简单。Drools 编程挑战许多医疗服务提供商使用案例管理系统作为跟踪医疗记录(比如护理、处方和评估)的一种经济高效的方法。我们的示例程序(基于这样一种系统)具有以下流程和需求:案例在系统内的所有临床医生之间循环。临床医生每周至少负责一个评估任务,或将通知发送给临床医生的监管员。系统给临床医生自动安排评估任务。如果案例未评估超过 30 天,则向案例组中的所有临床医生发送提醒。如果没有响应,系统会采取系统的业务规则定义的措施,比如通知出现问题的临床医生组并提出另一项计划。为该用例选择一个业务流程管理 (BPM) 工作流和规则引擎是有一定道理的:系统使用数据剖析/分析规则(已在上述列表中用斜体字标出),将每个案例用作在 jBPM 中长期运行的一个流程/工作流,而且我们可以使用一个 Drools Planner 来满足自动安排的需求。出于本文的目的,我们将只关注程序的业务规则。我们还要介绍的是系统需求,在满足规则条件时立即实时生成提醒和通知。因此这是一个持续的实时数据分析用例。 显示了在我们的系统中声明的三个实体类:MemberCase、Clinician 和 CaseSupervision:清单 1. 实体类@Entity
@EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class})
public class MemberCase implements Serializable
private L // pk
private Date startD
private Date endD
private M // not null (memberId)
private List&CaseSupervision& caseSupervisions = new ArrayList&CaseSupervision&();
@EntityListeners({DefaultWorkingMemoryPartitionEntityListener.class})
public class Clinician implements Serializable
private L // pk
private List&CaseSupervision& caseSupervisions = new ArrayList&CaseSupervision&();
@EntityListeners({SupervisionStreamWorkingMemoryPartitionEntityListener.class})
public class CaseSupervision implements Serializable
private L // pk
private Date entryD
private MemberCase memberC
}MemberCase 中的每个实例代表一个病历。Clinician 代表机构中的临床医生。临床医生每次进行案例评估时会产生一个 CaseSupervision 记录。同时,这三个实体是将要定义的业务规则中的事实类型。还要注意的是,上述 CaseSupervision 被声明为 Drools 中的一个事件类型。从应用程序的角度来看,我们可以从系统的任何地方、在不同的屏幕上、在不同的工作流中修改这三种类型的实体。我们甚至可以使用 Spring Batch 这样的工具来批量更新实体。然而,出于本例的考虑,让我们假设将只通过 JPA 持久上下文来更新实体。注意,样例应用程序是一个 Spring-Drools 集成,它使用 Maven 来完成构建。本文稍后将考虑一些配置细节,但是您可以随时 。现在,让我们考虑一些使用 Drools 5 的概念特性。事实和 FactHandle规则引擎的一般概念是:事实 (fact) 是规则所依赖的数据对象。在 Drools 中,事实是从应用程序获得且断言为引擎的工作内存的任意 Java bean。或者说,就像在 JBoss Drools
中撰写的那样:规则引擎根本没有 “克隆” 事实,它是一天结束时的所有引用/指针 (pointer)。事实是您的应用程序数据。没有 getter 和 setter 的 Strings 和其他类不是有效的 Fact,不能和 Field Constraints 一起使用,Field Constraints 依靠 getter 和 setter 的 JavaBean 标准与对象进行交互。 除非您在规则之上已经指定了关键字 no-loop 或 lock-on-active,否则在工作内存中发生事实更改时,不能在任何时候重新评估 Drools 规则引擎中的规则。您还可以使用 @PropertyReactive 和 @watch 注释来指定事实属性,Drools 应该监视这些属性来应对更改。Drools 会忽略对事实的其他所有属性的更新。出于实际维护的目的,有三种方法来安全更新 Drools 工作内存中的事实:在 Drools 语法中,右边 (RHS) 是规则的操作/结果部分,您可以在 modify 块内对其进行更新。将一个事实更改为将要激活的规则的结果时,会使用这个方法。从外部通过 Java 类中的 FactHandle;用于由应用程序 Java 代码执行的事实更改。让 Fact 类实现 PropertyChangeSupport,就像 JavaBeans 规范定义的那样;使用此方法将 Drools 注册为 Fact 对象的 PropertyChangeListener。作为安静的观察者,我们的规则不会更新 Drools 工作内存中的任何 JPA 实体事实;相反,它们会将逻辑事实生成为推理结果。(参见下列的 。)但是,更新规则中的 JPA 实体时需要特别注意,因为更新的实体可能处于分离状态,或者没有事务或只读事务与当前线程有关联。因此,对实体所做的更改将不会保存到数据库中。尽管事实对象是因为引用而被传递,Drools(与 JPA/Hibernate 不同)不能跟踪超出规则之外的事实更改。您可以通过使用 FactHandle 来通知 Drools 有关在应用程序 Java 代码中所做的事实更改,从而避免产生不一致的规则推理结果。Drools 接着会对规则进行适当的重新评估。FactHandle 是表示您在工作内存中断言的事实对象的标记。这就是您希望修改或取消一个事实时与工作内存的正常交互方式。在样例应用程序( 和 )中,我们使用 FactHandle 来操作工作内存中的实体事实。您可以通过实现 PropertyChangeSupport(它捕获了对 bean 的属性所做的每一项更改)来解决 Drools 无法跟踪事实更改的问题。但是,请记住,这也是随后由于执行频繁的规则重新评估而需要解决的性能问题。使用 JPA 实体作为事实您可以通过 POJO 事实将 JPA 实体作为域数据对象插入到 Drools 的工作内存中。这样做可以让您避免对 Value Object/DTO 层以及 JPA 实体和 DTO 之间的相应转换层进行数据建模。将实体用作事实会简化应用程序代码,您必须额外注意 “实体-生命周期” 阶段。实体事实应当保存为受管(持久)状态或分离状态。永远不要将临时的实体插入到 Drools 工作内存中,因为它们还未保存到数据库中。同样,应当从工作内存中收回已删除的实体。否则应用程序数据库和规则引擎的工作内存会不同步。因此,这会带来一些严重的问题:我们如何才能有效通知规则引擎有关通过 FactHandle 在应用程序代码中所做的实体更改?命令式(Imperative)编程与 AOP 的比较如果想通过命令式编程的方式来应对这个挑战,我们需要结束在紧邻相应 JPA API 方法的知识会话上调用 insert()、update() 和 retract() 方法。这种方法应该是 Drools API 的一种入侵性用法,而且应将 spaghetti 代码留在应用程序中。更糟糕的是,JPA 中已更新的(脏)实体在读取/写入事务结束时会自动与数据库同步运行,未对持久上下文进行任何显式调用。我们如何才能拦截这些更改并通知给 Drools?另一个选择是,在单独的进程中轮询实体更改,就像典型的商业智能 (BI) 工具所做的那样,这会使核心业务功能保持干净,但实现此操作很困难,成本较高,结果也不会是即时的。JPA EntityListener 是一种 AOP 拦截器,非常适合我们的用例。在
中,我们将定义两个 EntityListener,它们会拦截对应用程序中三种类型的实体所做的所有更改。这种方法使得 JPA 中实体 的生命周期与其在 Drools 中的生命周期不断地保持同步。在 “实体-生命周期” 回调方法中,我们为给定的实体实例查找一个 FactHandle,然后根据 JPA 生命周期阶段通过返回的 FactHandle 来更新或收回事实。如果 FactHandle 缺失,则会将实体作为新事实 插入到工作内存中,以实现事实更新或持久保存。由于工作内存中不存在实体,因此在调用 JPA 删除时也没有必要将其从工作内存中删除。 中所示的两个 JPA EntityListener 用于工作内存中的两个不同的入口点,或者分区。第一个入口点在 MemberCase 和 Clinician 之间共享,第二个入口点用于 CaseSupervision 事件类型。清单 2. EntityListeners@Configurable
public class DefaultWorkingMemoryPartitionEntityListener
@Value("#{ksession}") //unable to make @Configurable with compile time weaving work here
private StatefulKnowledgeS
@PostPersist
@PostUpdate
public void updateFact(Object entity)
FactHandle factHandle = getKsession().getFactHandle(entity);
if(factHandle == null)
getKsession().insert(entity);
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)
// a workaround for @Configurable
setKsession(ApplicationContextProvider.getApplicationContext()
.getBean("ksession", StatefulKnowledgeSession.class));
@Configurable
public class SupervisionStreamWorkingMemoryPartitionEntityListener
@Value("#{ksession}")
private StatefulKnowledgeS
@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 一样, 中的 EntityListener 方法使系统的核心业务逻辑保持干净。注意,这种方法需要一个或多个 Drools 全局知识会话来注入到两个 EntityListener 中。在本文的后面部分中,我们将一个知识会话声明为一个单态(singleton)的 Spring bean。初始化工作内存在启动应用程序后,三种实体类型的所有现有记录都将从数据库预加载到用于规则执行的工作内存中,如
所示。从那时起,会向工作内存通知通过两个 EntityListener 对实体所做的任何更改。清单 3. 初始化工作内存并运行 Drools 查询@Service("droolsService")
@Lazy(false)
@Transactional
public class DroolsServiceImpl
@Value("#{droolsServiceUtil}")
private DroolsServiceUtil droolsServiceU
@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);
public class DroolsServiceUtil
@Value("#{ksession}")
private StatefulKnowledgeS
public void fireRulesUtilHalt()
getKsession().fireUntilHalt();
}catch(ConsequenceException 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)
for(FactHandle factHandle : factHandles)
getKsession().retract(factHandle);
factHandles = getKsession().getFactHandles(
new ObjectFilter(){public boolean accept(Object object)
if(object instanceof Clinician)
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);
}有关 fireAllRules() 的注意事项请注意,在
中,我们拥有在各个 EntityListener 的回调方法中调用 fireAllRules() 的选项。在一个急切加载 (eager-loaded) 的 Spring bean 的 “@PostConstruct” 方法中,我只调用了一次 fireUntilHalt() 方法就简化了这一步骤。fireUtilHalt 方法应该在单独的线程中调用一次(查看 Spring 的 @Async 注释),随后不断触发规则激活,直至调用停止。如果没有需要触发的激活,fireUtilHalt 会等待将激活添加到一个激活议程组或规则流组中。我可以选择在应用程序的 Spring XML 配置文件(如下所示)中触发规则,甚至是启动流程。然而,我在尝试配置 fireUntilHalt() 方法时检测到了一个可能的线程处理问题。在对懒惰式加载 (lazy-loading) 实体关系进行规则评估期间,结果是一个 “数据库连接已关闭的错误”()。Spring-Drools 集成现在,让我们花一些时间来看看 Spring-Drools 集成的一些配置细节。 是应用程序的 Maven pom.xml 的一个代码段,包括用于 Drools 内核、Drools 编译器和 Drools Spring 集成包的依赖关系:清单 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&身份与等同性的比较在
中,我将一个全局有状态知识会话配置为一个单态的 Spring bean。(一个无状态知识会话不会充当一个持续时间很长的会话,因为它在迭代调用期间没有保持其状态。) 中需要注意的一个重要设置是 &drools:assert-behavior mode="EQUALITY" /&。在 JPA/Hibernate 中,托管实体将与身份(identity) 进行比较,而分离的实体将与等同性(equality) 进行比较。插入到有状态实体会话中的实体快速从 JPA 角度分离。因为与单态的有状态知识会话的生命期相比,一个事务范围的持续上下文,甚至是一个 “扩展的” 或 “流范围的” 持续上下文(参见 )是临时的。每次通过不同的持续上下文对象取得的同一个实体是不同的 Java 对象。默认情况下,Drools 使用的是身份比较。因此,当通过 ksession.getFactHandle(entity) 在工作内存中的现有实体事实上查看 FactHandle 时,Drools 很可能不会找到匹配项。为了与分离的实体相匹配,我们必须在配置文件中选择 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" /&看看应用程序源代码,了解更完整的配置细节。Drools 规则 定义了两个复杂的事件处理 (CEP) 规则。除了类似 JPA 的两个事实类型之外,MemberCase 和 Clinician,CaseSupervision 实体类被声明为一个事件。临床医生执行的每个案例评估任务都会生成一个 CaseSupervision 记录。创建记录之后,不可能一直不断地对其进行更改。 中的 Case Supervision 规则的条件可用来测试在过去的 30 天内案例上是否已经存在案例监督。如果没有,规则的结果/措施部分会生成一个 TransientReminder 事实(在
中定义),并从逻辑上将事实插入工作内存。Clinician Supervision 规则指示,临床医生在过去的七天内应当已经完成至少一个案例监督;如果没有完成,规则的结果/措施部分会生成一个类似的 TransientReminder 事实,并从逻辑上插入到工作内存中。清单 6. 案例监督规则package ibm.developerworks.article.
import ibm.developerworks.article.drools.service.*
import ibm.developerworks.article.drools.domain.*
global DroolsServiceUtil droolsServiceU
declare Today
@role(event)
@expires(24h)
declare CaseSupervision
@role(event)
@timestamp(entryDtm)
rule "Set Today"
timer (cron: 0 0 0 * * ?)
salience 99999
// optional
insert(new Today());
rule "Case Supervision"
dialect "mvel"
$today : Today()
$memberCase : MemberCase(endDtm == null, startDtm before[30d] $today)
not CaseSupervision(memberCase == $ memberCase)
over window:time(30d) from entry-point SupervisionStream
insertLogical(new TransientReminder($memberCase, (Clinician)null,
"CaseReminder", "No supervision on the case in last 30 days."));
query "CaseReminderQuery"
$caseReminder : TransientReminder(reminderTypeCd == "CaseReminder")
rule "Clinician Supervision"
dialect "mvel"
$clinician : Clinician()
not CaseSupervision(clinician == $clinician)
over window:time(7d) from entry-point SupervisionStream
insertLogical(new TransientReminder((MemberCase)null, $clinician,
"ClinicianReminder", "Clinician completed no evaluation in last 7 days."));
query "ClinicianReminderQuery"
$clinicianReminder : TransientReminder(reminderTypeCd == "ClinicianReminder")
end请注意, 中所示的 TransientReminder 事实不是一个 JPA 实体,而是一个常规的 POJO。清单 7. TransientReminderpublic class TransientReminder implements Comparable, Serializable
private MemberCase memberC
private String reminderTypeCd;
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 上下文中是不可变的。如果一个事件受更改的制约,那么更改(描述为 “事件数据补充”)不应当影响规则执行的结果。这就是我们在 CaseSupervision 的 EntityListener 中只监视 @PostPersist 实体生命周期阶段的原因(参见 )。Drools 对 Sliding Windows 协议的支持使得事件对时态推理特别有用。滑动窗口 是为感兴趣的事件制定作用域的一种方式,就好像它们属于一个不断移动的窗口一样。两种最常见的滑动窗口实现是基于时间的窗口和基于长度的窗口。在
中所示的样例规则中,over window:time(30d) 建议,过去 30 天中创建的 CaseSupervision 事件由规则引擎进行评估。一旦过了 30 天,不可变的事件将永远不会再次进入到窗口中,而且 Drools 将自动从工作内存中收回这些事件,并对相应的规则进行重新评估。由于事件是不可变的,所以 Drools 会自动管理事件生命周期。因而事件比事实更具有内存效率。(但是,请注意,您必须在 Drools-Spring 配置中将事件处理模式设置为 STREAM;否则滑动窗口之类的时态操作符会停止工作。)使用已声明的类型在
中需要注意的其他一些事项包括:MemberCase 事实(不属于事件类型)也针对事件约束进行了评估,就像我们只评估之间 30 多天内创建的案例一样。某个案例可能今天已经存在 29 天了,但明天就是 30 天了,这就意味着必须在每一天的一开始就对 Case Supervision 规则进行重新评估。不幸的是,Drools 并不提供 “今天” 变化的量。因此,作为一个变通方案,我添加了一个名为 Today 的事件类型;这是一个 Drools 声明类型,或是一个用规则语言(而不是用 Java 代码)声明的数据构造。这种特殊的事件类型根本不声明任何显式属性,除了一个隐式的 @timestamp 元数据,后者是在 Today 事件断言到工作内存中时进行自动填充。另一个元数据 @expires(24h) 指定,某个 Today 事件会在断言后的 24 小时内到期。要想在每天的一开始重设 Today,还需要在 Set Today 规则之上添加了一个 timer。先激活这个规则,然后就会在每天的一开始触发该规则来插入一个新鲜的 Today 事件,该事件将取代刚刚期满的事件。随后,新鲜的 Today 事件会触发 Case Supervision 规则的重估。还要注意的是,如果规则的条件中没有出现事实更改,那么计时器本身无法触发规则的重估。计时器也不能重新评估函数或内联的 eval,因为 Drools 将这些构造函数的返回结果作为时间常量,并缓存它们的值。何时使用事实与事件的比较了解事实和事件之间的区别有助于我们轻松决定何时使用每种类型:在某个时间点或某段持续时间内,当数据表示系统状态的不可变快照时,应该将事件 用于场景,该事件是时间敏感的,并且会很快到期,或可以预计数据量会快速且持续增长。在数据对业务域至关重要的地方,以及数据将体验正在进行的更改并且这些更改要求持续重估规则的时候,应该将事实 用于场景。Drools 查询下一个步骤是提取规则执行结果,这通过查询工作内存中的事实来完成。(一种替代方法是通过调用规则语法右手边的 global 上的方法,让规则引擎将结果传递给应用程序。)在这个例子中,事实断言和规则触发都立即发生,没有任何延迟,这就确保了我们在
中的查询会返回实时的报告。因为 TransientReminder 事实是通过逻辑方式断言的,所以在它们的条件再无法得到满足时,规则引擎会自动从工作内存中收回它们。可以说提醒 是在早上由一个规则引擎在特定的案例上生成的。随后,我们在 Java 代码中执行查询 “CaseReminderQuery”,如
所示,因此会返回一个提醒,并向系统中的所有临床医生显示该提醒。如果在下午某个临床医生完成了案例上的一个评估,并生成了一个新的案例监督记录,该事件会打破用于提醒 事实的条件。Drools 随后会自动收回它。我们要确定该提醒事实消失了,方法是在完成案例评估之后立即运行相同的查询。逻辑断言可以使推理结果保持最新,而且规则引擎运行在内存效率模式之下,这与事件的行为非常相像。现场查询 更是锦上添花。让一个现场查询处于打开状态,这会创建一个查询结果视图,并发布给定视图内容的更改事件。这意味着现场查询恰好需要运行一次,由此产生的结果视图会使用由规则引擎发布的正在进行的更改来实现自动更新。到目前为止,您可能已经发现,只需一点点有关 Drools、JPA 和 Spring 的背景知识,就可以轻松实现一个持续的实时数据分析应用程序。我们将通过一些将改进我们的案例管理解决方案的高级编程步骤来结束本文。高级 Drools 编程管理关系FactHandle 的一个有趣的约束条件是它只与当前事实相关联,与事实的嵌套关系没有关联。Drools 会通过其在 getKsession().update(factHandle, memberCase) 中的 FactHandle,了解对 MemberCase 的 id(尽管这永远不可能发生,因为主键是不可变的)、startDtm 或 endDtm 所做的更改。然而,在调用同一个方法时,不会通知 Drools 有关对 member 和 caseSupervisions 属性所做的更改。同样,系统不会通知 JPA 中的 EntityListener 通知有关一对多和多对多关系的更改。这是因为外键位于相关 表或链接 表中。为了根据更新的事实与这些关系建立连接,我们可以构建递归逻辑,获取每个嵌套关系的 FactHandle。一个更好的解决方案是将 EntityListener 放置在与规则条件有关中的所有实体上(包括链接表)。我们使用 Member 和 CaseSupervision 来完成此操作,其中更改由每个实体自己的 EntityListener 和 FactHandle 来处理的(参见
和 )。规则评估期间的实体懒惰式加载除非我们已经指定一个知识库分区(也就是说,可以执行并行处理),否则不会在调用 ksession.insert()、ksession.update() 或 ksession.retract() 的同一个线程中对规则进行评估。 和
中的事实断言都发生在事务上下文中,在该上下文中,事务范围的 JPA 持久上下文(Hibernate 会话)是可用的。这就允许规则引擎跨懒惰式加载实体关系进行评估。如果启用了一个知识库分区,则必须将实体关系配置为急切加载,以避免产生 JPA LazyInitializationException。启用事务默认情况下,Drools 不支持事务,因为它在工作内存中不保存任何历史快照。这对我们的 EntityListener 来说是一个问题,因为生命周期回调方法是在数据库刷新之后、但在事务提交之前调用的。如果事务被回滚又会怎么样呢?如果那样的话,JPA 持久上下文中的实体将变为分离的实体,且与数据库表中的行不一致,而且工作内存中的行也是如此。规则引擎推理结果将不再是可信的。启用事务通过确保工作内存中的数据库和应用程序数据库始终同步,且规则推理结果始终准确,使我们的案例管理系统具有防弹功能。在 Drools 中,适当应用 JPA 和 JTA 实现以及类路径中的一个 “drools-jpa-persistence” 包,可以配置一个 JPAKnowledgeService(参阅 )来创建我们的有状态知识会话。具有流程实例、变量和事实对象的整个有状态知识会话可以映射为表 “SessionInfoThe”(将 ksessionId 作为主键)中行的一个二进制列。当我们通过注释或 XML 在应用程序中指定事务边界时,应用程序启动的事务会传播到规则引擎。无论什么时候发生事务回滚,有状态知识会话都会恢复到数据库中保存的以前的状态。这维护了应用程序数据库和 Drools 数据库之间的一致性和集成。当同时从多个 JTA 事务中进行访问时,内存中的单个有状态知识会话应当像 REPEATABLE READ 一样运转;否则,单个 SessionInfo 实体实例可能具有一些从不同事务所做的混合状态更改,这会打破事务划分。请注意,自编写 REPEATABLE READ 起,就不能确定它是否能够通过 drools-jpa-persistence 包的事务管理器来实现。集群如果应用程序要在集群环境下运行,前面描述的方法很快就会失败。每个嵌入式规则引擎的实例都会接收同一个节点上发生的实体事件,这会导致不同节点上的工作内存不同步。我们可以使用一个通用的远程 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 的更多信息。
下载资源 (j-drools5-src.zip | 5KB)相关主题:本文中使用了 Drools Expert 和 Drools Fusion,它们可以实现规则引擎和 CEP 框架。:了解 Drools 项目的新功能和事件。:JBoss 社区的在线文档和 PDF 文档。“”(Ricardo Olivieri,IBM developerWorks,2008 年 3 月):对于 Drools 初学者,可以从这里了解如何使用开源的 Drools 规则引擎让 Java 应用程序变得更适合更改。“”(Sing Li,IBM developerWorks,2006 年 8 月):Spring 2 与 JPA 的集成使其成为 EJB 3.0 规范的基础。本文是面向初学者的有关 Spring 和 JPA 的实验操作介绍。“”(Xinyu Liu,IBM developerWorks,2010 年 4 月):有关 Spring Web Flow 2 中持久技术的深入讨论。 以在线 Drools 演示和教程为特色。“”(Wolfgang Kulhanek 和 Carol Serna,IBM developerWorks,2005 年 9 月):了解有关 WebSphere Process Server 及其功能、特性和解决方案的信息。“”(Ali Manji,IBM developerWorks,2011 年 6 月):使用 Rational Application Developer 工具从 Derby 数据库中快速生成 JPA 实体并进行自定义,该数据库带有使用 Identity Value Generation 性能的表格。“”(David Van Couvering,Java.net,2007 年 4 月):了解有关 JPA 中事务回滚的更多信息,这在本文的后面部分中已经讨论过。“”(Xinyu Liu,JavaWorld,2008 年 7 月):了解有关 Hibernate Search 及其 POJO 编程模型集成的更多信息。(Michal Bali,PACKT Publishing,2009 年 7 月):发现 Drools 作为管理业务规则平台的能力。(Lucas Amador,PACKT Publishing,2012 年 1 月):用于使用 Drools 5 Expert、Fusion、Guvnor、Planner 和 jBPM5 开发技能的资源。:这里有数百篇关于 Java 编程各个方面的文章。
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=http://www.ibm.com/developerworks/js/artrating/SITE_ID=10Zone=Java technology, Open sourceArticleID=831053ArticleTitle=使用 Drools 和 JPA 实现持续的实时数据分析publish-date=}

我要回帖

更多关于 4444kt页面访问升 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信