在Activiti中集成JPA(解决动态表单生成的大量数据)

1. 为何集成JPA

在《比较Activiti中三种不同的表单及其应用》一文中介绍了不同表单的特点以及表现形式,相信这是每个初学者都会面临表单类型的选择。

如果选择了使用动态表单那么将面临一个比较“严峻”的问题——大数据量,我们知道动态表单的内容都保存在一张表中(ACT_HI_DETAIL),我们也清楚动态表单中每一个Field都会在该表中插入一条记录,假如一个流程共有20个字段,这个数据量大家可以计算一下,每天多少个流程实例,每个月、每年多少?

日积月累的大数据会影响系统的性能,尤其涉及到关联查询时影响更深,除了性能之外动态表单还有一个弊端那就是数据是以的形式存储没有任何数据结构可言,流程运行中生成的数据很难被用于分析、查询,如何破解嘞?

2. 如何集成JPA

Activiti除了核心的Engine之外对企业现有的技术、平台、架构都有所支持,对于业务实体的持久化当然也会有所支持,那就是EJB的标准之一)——JPA,引擎把JPA的API引入到了内部,使用JPA功能的时候只需要把entityManagerFactory配置到引擎配置对象(参考:谈谈Activiti的引擎与引擎配置对象)即可。

参考用户手册的JPA章节,介绍了引擎配置对象中的几个jpa有关的属性,如下:

  • jpaPersistenceUnitName: 使用持久化单元的名称(要确保该持久化单元在类路径下是可用的)。根据该规范,默认的路径是/META-INF/persistence.xml)。要么使用 jpaEntityManagerFactory 或者jpaPersistenceUnitName。
  • jpaEntityManagerFactory: 一个实现了javax.persistence.EntityManagerFactory的bean的引用。它将被用来加载实体并且刷新更新。要么使用jpaEntityManagerFactory 或者jpaPersistenceUnitName。
  • jpaHandleTransaction: 在被使用的EntityManager 实例上,该标记表示流程引擎是否需要开始和提交/回滚事物。当使用Java事物API(JTA)时,设置为false。
  • jpaCloseEntityManager: 该标记表示流程引擎是否应该关闭从 EntityManagerFactory获取的 EntityManager的实例。当EntityManager 是由容器管理的时候需要设置为false(例如 当使用并不是单一事物作用域的扩展持久化上下文的时候)。

2.1 配置持久化单元或者EntityManagerFactory

要在引擎中使用JPA需要提供EntityManagerFactory或者提供持久化单元名称(引擎会自动查找最终获取到EntityManagerFactory对象),在使用的时候可以根据自己的实际情况进行选择,在kft-activiti-demo中使用了jpaEntityManagerFactory属性注入EntityManagerFactory对象的方式。

2.2 Standalone模式的JPA配置

?


1

2

3

4

    <property

name
="jpaPersistenceUnitName"

value
="kft-jpa-pu">

    <property

name
="jpaHandleTransaction"

value
="true"></property>

    <property

name
="jpaCloseEntityManager"

value
="true"></property>

</property>

2.3 Spring(托管)模式的JPA配置

?


1

2

3

4

    <property

name
="jpaEntityManagerFactory"

ref
="entityManagerFactory">

    <property

name
="jpaHandleTransaction"

value
="false"></property>

    <property

name
="jpaCloseEntityManager"

value
="false"></property>

</property>

3. 实例分析

在最新版本(1.10)的kft-activiti-demo中添加了JPA演示,大家可以从Github上下载源码查看源码。

3.1 相关说明

  • 流程定义文件:leave-jpa.bpmn
  • 实体文件:me.kafeitu.demo.activiti.entity.oa.LeaveJpaEntity
  • 实体管理器:me.kafeitu.demo.activiti.service.oa.leave.LeaveEntityManager

3.2 创建实体

在流程定义文件中定义了一个流程的start类型监听器:

?


1

2

3

<extensionelements>

  <activiti:executionlistener

event
="start"

expression
="${execution.setVariable('leave',
leaveEntityManager.newLeave(execution))}"
></activiti:executionlistener>

</extensionelements>

这个监听器的触发的时候会执行一个表达式,调用名称为leaveEntityManager的Spring Bean对象的newLeave方法,并且把引擎的Execution对象传递过去,得到一个LeaveJpaEntity对象后设置到引擎的变量中(名称为leave)。

下面是LeaveEntityManager.java的代码:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

@Entity(name
=
"LEAVE_JPA")

public

class

LeaveJpaEntity
implements

Serializable {

 

    private

Long id;

    private

String processInstanceId;

    private

String userId;

    private

Date startTime;

    private

Date endTime;

    private

Date realityStartTime;

    private

Date realityEndTime;

    private

Date reportBackDate;

    private

Date applyTime;

    private

String leaveType;

    private

String reason;

 

    /**

     *
部门领导是否同意

     */

    private

String deptLeaderApproved;

 

    /**

     *
HR是否同意

     */

    private

String hrApproved;

     

    ...

}

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

@Service

public

class

LeaveEntityManager {

 

    @PersistenceContext

    private

EntityManager entityManager;

     

    /*
把流程变量的值赋值给JPA实体对象并保存到数据库 */

    @Transactional

    public

LeaveJpaEntity newLeave(DelegateExecution execution) {

        LeaveJpaEntity
leave =
new

LeaveJpaEntity();

        leave.setProcessInstanceId(execution.getProcessInstanceId());

        leave.setUserId(execution.getVariable("applyUserId").toString());

        leave.setStartTime((Date)
execution.getVariable(
"startTime"));

        leave.setEndTime((Date)
execution.getVariable(
"endTime"));

        leave.setLeaveType(execution.getVariable("leaveType").toString());

        leave.setReason(execution.getVariable("reason").toString());

        leave.setApplyTime(new

Date());

        entityManager.persist(leave);

        return

leave;

    }

 

    public

LeaveJpaEntity getLeave(Long id) {

        return

entityManager.find(LeaveJpaEntity.
class,
id);

    }

 

}

当启动流程后查看表LEAVE_JPA中的数据与表单填写的一致。

3.3 在流程中更改实体的值

部门领导或者人事审批节点完成时需要把审批结果更新到LeaveJpaEntity属性中(即更新表LEAVE_JPA),所以在这两个任务上添加一个complete类型的监听器,如下所示:

?


1

2

3

4

5

6

7

8

9

10

11

<usertask

id
="deptLeaderAudit"

name
="部门领导审批"

activiti:candidategroups
="deptLeader">

    <extensionelements>

        <activiti:tasklistener

event
="complete"

expression
="${leave.setDeptLeaderApproved(deptLeaderApproved)}"></activiti:tasklistener>

    </extensionelements>

</usertask>

 

<usertask

id
="hrAudit"

name
="人事审批"

activiti:candidategroups
="hr">

    <extensionelements>

        <activiti:tasklistener

event
="complete"

expression
="${leave.setHrApproved(hrApproved)}"></activiti:tasklistener>

    </extensionelements>

</usertask>

3.4 流程结束后删除表单数据

熟悉Activiti表的应该知道表单数据会保存在表ACT_HI_DETAIL中,特性是字段TYPE_字段的值为FormProperty,我们只要根据流程实例ID过滤删除记录就可以清理掉已经结束流程的表单数据。

在最新版本的Demo中(1.10版本)添加了一个类用来执行SQL:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

@Component

public

class

ActivitiDao {

 

    @PersistenceContext

    private

EntityManager entityManager;

 

    /**

     *
流程完成后清理detail表中的表单类型数据

     *
@param processInstanceId

     *
@return

     */

    public

int

deleteFormPropertyByProcessInstanceId(String processInstanceId) {

        int

i = entityManager.createNativeQuery(
"delete
from act_hi_detail where proc_inst_id_ = ? and type_ = 'FormProperty' "
)

                .setParameter(1,
processInstanceId).executeUpdate();

        return

i;

    }

 

}

流程中定义了一个流程级别的结束监听器me.kafeitu.demo.activiti.service.oa.leave.LeaveProcessEndListener

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Service

@Transactional

public

class

LeaveProcessEndListener
implements

ExecutionListener {

 

    protected

Logger logger = LoggerFactory.getLogger(getClass());

 

    @Autowired

    ActivitiDao
activitiDao;

 

    @Override

    public

void

notify(DelegateExecution execution)
throws

Exception {

        String
processInstanceId = execution.getProcessInstanceId();

 

        int

i = activitiDao.deleteFormPropertyByProcessInstanceId(processInstanceId);

        logger.debug("清理了
{} 条历史表单数据"
,
i);

    }

}

3.5 已知问题(未解决)

图中的三条数据因为是在销假任务完成后设置的,不知道是不是引擎的Bug导致插入这三个表单属性比调用流程结束监听器还晚(从引擎的日志中可以分析出来)导致这三条记录不能被删除,因为在删除的时候这三条数据还没有插入到数据库。

这个问题后面会继续跟踪,解决了会在这里更新!!!

时间: 2024-11-10 00:59:48

在Activiti中集成JPA(解决动态表单生成的大量数据)的相关文章

Activiti中三种不同的表单及其应用

这个恐怕是初次接触工作流最多的话题之一了,当然这个不是针对Activiti来说的,每个工作流引擎都会支持多种方式的表单.目前大家讨论到的大概有三种. 动态表单 外置表单 普通表单 具体选择哪种方式只能读者根据自己项目的实际需求结合现有技术或者架构.平台选择!!! 1.动态表单 这是程序员最喜欢的方式,同时也是客户最讨厌的--因为表单完全没有布局,所有的表单元素都是顺序输出显示在页面. 此方式需要在流程定义文件(bpmn20.xml)中用activiti:formProperty属性定义,可以在开

struts2中Action怎样获取动态表单中的多行数据

问题描述 一: struts-config.xml 配置<struts-config> <form-beans> <form-bean name="trafficForm" type="com.ccit.safetm.controller.traffic.TrafficForm"></form-bean> </form-beans> <action-mappings> <action pa

ie 8中onsubmit return false 无效问题的解决,表单还是提交了

问题描述 ie 8中onsubmit return false 无效问题的解决,表单还是提交了 ie 8中onsubmit return false 无效问题的解决,表单还是提交了 解决方案 IE8的bughttp://www.gbtags.com/gb/share/2432.htm 有人和你问题相同.用按钮代替吧. 解决方案二: 代码呢..无效说明你代码有问题报错了,没有执行到return false语句.注意你的return false一定要在onsubmit事件中,不要再其他回调中retu

基于动态表单的Java不确定字段数报表项目实现

最近在一个项目的实施过程中,由于客户是国内该行业业务的领导者,业务表格并没有真正的确定,一直在不断的完善.所以程序的业务表单一直被客户牵制,一直跟着业务更改,要想非常出色的支撑整个项目和实现真正意义上的业务表单与整个项目松耦合集成才能达到最好的实施效果和最高的开发效率.经过与公司总监.总工程师多次探讨,决定开发动态表单插件,以满足客户业务表格不断变化的需求. 动态表单插件,即为由客户确定业务表格,通过插件录入到动态表单核心程序,生成业务表单. 具体实现思路: 1.手工制作带有特殊标记的html页

asp.net 动态表单之数据分页_实用技巧

但是问题来了,不同科系的同学的科目是不一样的,那么我们在数据库设计的时候通常是把学生.某科成绩作为一条记录,那么这个时候我们就需要做一个行转列的逻辑处理了. 解决方法: 使用GridView来生成表单,这个实现起来会比较麻烦,如果要在列表里面显示链接就更不可能了: 生成html再输出到页面中,这个实现起来比较灵活.方便: 基本功能点: 动态生成表头: 数据进行分页: 查询数据: 对每个成绩进行超链接,查看明细:  页面代码 复制代码 代码如下: <div id="dataDiv1"

如何设计动态表单

问题描述 公司前辈要我为动态设计一个动态表单,他稍微和我说了一下,但我不是很了解他的意思不知道哪位仁兄做过这方面的研究,帮我解决下问题.比如说我 有个界面要出售电脑:电脑有不同的档次.我要在界面实现能够动态的为我的电脑档次增加 属性.如我单机"添加属性"按钮,就能显示一个对话框出来让我输入 属性的名称,字符类型,控件类型(如:是下拉列表还是 单选框等.还要考虑到下拉列表关联到的是数据库中其他表的属性(如 usb型号))高档电脑能够添加一个USB视频设备 的属性等,或则提供修改等.他说大

ASP.NET MVC 2生成动态表单的一种最简单的思路

在BPM.OA等系统中,都会存在一个表单设计器.有些是通过操作gridview来完成一个表单的设计:有些是通过类似VS拖拽的方法完成一个表单的设计.很明显后面一种优越于前面一种.无论是哪种,最后都会产生一些XML之类的表单结构的数据. 这篇文章将讲述,在表单设计器设计好表单之后,在ASP.NET MVC中如何将表单结构的xml转换成实际应用系统中的表单.看下面一个xml文件,我们假设它是由一个表单设计器设计出来的. <?xml version="1.0" encoding=&qu

利用JS屏蔽页面中的Enter按键提交表单的方法_javascript技巧

如在设置了JS代码响应<p>标签的Enter按键触发事件时,根据冒泡型事件原则该事件会一直传到<from>表单处,并将表单提交.这不是我们想要的效果,我们可以设置如下代码来加以屏蔽: $(document).keydown(function(event){ switch(event.keyCode){ case 13:return false; } }); 但是,如果页面中有按钮时在Opera浏览器中同样会提交表单,这是因为按钮在生成的HTML代码中是submit类型的,解决办法是

解决php表单重复提交实现方法_php技巧

重复提交是我们开发中会常碰到的一个问题,除了我们使用js来防止表单的重复提交,同时还可以使用php来防止重复提交哦. <?php /* * php中如何防止表单的重复提交 */ session_start(); if (empty($_SESSION['ip'])) {//第一次写入操作,判断是否记录了IP地址,以此知道是否要写入数据库 $_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; //第一次写入,为后面刷新或后退的判断做个铺垫 //...........