批量操作-Spring事务处理解决方案

1       修改历史

版本号

主要作者

完成日期

说明

时间hours

0.1

LevinSoft

2006-8-10

文件基本架构、主题内容

3

V1.0

LevinSoft

2006-11-8

细化步骤

2

V1.1

LevinSoft

2007-8-10

细化步骤、改造结构、结论、删除多余的分析

2

2       目的和范围

本文档通过一个Spring事务问题,通过渐进式的分析和解决,最终得出结论。同时,对Hibernate的Session刷新机制、Spring的事务处理机制、批量操作和单条数据的操作的相同和不同点也进行了分析。

主要包括的内容:

1. 介绍

2. 问题描述

3. 问题分析,配合介绍了相关Spring和Hibernate的技术点。

4. 不同的方案

5. 方案的比较

6. 结论、展望、限制

7. 参考资源

3       介绍

对于系统,存在着对大量数据操作需求。对于大量数据的操作,主要包括:批量导入、批量导出、批量删除。

批量的操作,主要涉及到技术主要包括:文件处理、事务管理、批量日志管理、处理和其他系统的接口。这些操作相关配合才能完成。

Spring & Hibernate事务管理,是其他操作的基础。如下图所示:


Spring & Hibernate事务管理



文件处理



其他接口处理



批量日志管理


4       关于事务管理的一个问题描述

整个的过程是按照一步一步分析和解决的思维方式进行组织的。

1. 问题描述

2. 分析问题,找到解决问题的必须要满足的条件。同时,有相关技术的分析。

3. 尝试的一些解决办法。仅描述了关键的步骤。

4. 修改code和配置文件。

5. 阶段性测试,测试结果分析。

6. 进一步修改

7. 最终成功结果

8. 解决方法评估。

4.1    基本情况描述

4.1.1            前提条件

1.每处理一条数据时,都需要调用其他系统的一个webservice接口,才能完成。

4.1.2            批量操作一般流程

对于批量操作,可以设计抽象类,例如:BatchProcessDao,采用Template Method设计批量操作的一些基本步骤。

4.1.2.1             批量导入

基本流程:

1. 在控制(Action)层,利用相关的工具类,完成源文件的上传操作。

2. 通过Abastract BatchProcessDao和具体子类配合,处理文件中数据,然后入库。

a)      循环从文件中读取数据,每次读取一行数据。

b)      对这行数据,进行验证。把错误数据保存。这在子类Dao实现。

c)      对正确格式数据,组成PO,

d)      验证PO。在子类完成。

e)      创建PO和插入到DB。

f)      调用其他系统webservice接口。

3. 如果存在不合法的数据。保存错误数据到文件,产生新的错误文件名称,然后上传到Webserver。

4. 记录操作日志。

4.2    批量删除和批量导出

4.2.1            与批量导入类似。

4.2.2            Resion日志

2006-08-09 23:55:23,048 DEBUG - [BatchProcessDao]

 == the errMessage is:批量导入操作的批次号为:310 批处理数据的总行数为:2 成功操作的纪录行数为:0 不成功的记录数为:2

1:6366,0901:Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO respectively remove 'readOnly' marker from transaction definition

2:6326,0901:Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO respectively remove 'readOnly' marker from transaction definition

 

2006-08-09 23:55:23,058 DEBUG - [BatchProcessDao]

 == end of the saveBatchErrResultToFile()

2006-08-09 23:55:23,058 DEBUG - [BatchProcessDao]

 == start of the logBatchResult()

2006-08-09 23:55:23,058 DEBUG - [BaseHibernateDao] 保存Hibernate的PO对象:com.qnuse.usboss.po.CfgBatchOperationLog

2006-08-09 23:55:23,058 WARN  - [BaseHibernateDao] Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO respectively remove 'readOnly' marker from transaction definition

5       问题分析

5.1    配置文件的配置

在Spring的ApplicationContext.xml文件中,transactionManager配置中,有这么一行配置:<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>。将对service中的batchImport,batchDelete函数,恰好符合这个条件。表明是个只读的事务。

5.2    对配置文件解释和分析

a)      表示方法必须是运行在事务中、并且是只读事务。

b)      readOnly表示:如果采用hibernate作为持久化机制,声明一个只读事务将使Hibernate的flush模式(FlushMode)为FLUSH_NEVER。这就告诉Hibernate避免和数据库进行不必要的对象同步,将所有的更新延迟到事务的结束。

c)      通过测试验证,在系统启动时,Hibernate Session的刷新模式就是:FLUSH_NEVER。

d)      参考了:《精通Hibernate:Java对象持久化技术详解》177页。对Hibernate的Session FlushMode进行了详细的解释。

5.3    对Session刷新的理解和分析

详细的细节参见:《精通Hibernate:Java对象持久化技术详解》177页

1. Session在清理缓存时(flush())时,将按照一定的顺序执行Sql。

2. Session在一些默认情况下将清理缓存。

3. Session的setFlushMode可以设定清理缓存的时间点。

4.    Session的commit方法会先调用flush(),然后提交事务。提交事务意味着对数据库所做的更新被永久保存下来。

6       解决方法一:在代码中启动一个新事务

6.1    修改刷新模式和事务管理

通过上面的分析:

1. 从异常提示来看,如果使得一个PO入库,应该:取掉readOnly模式(FlushMode.NEVER)的方式。 进行修改:

a)      方法一:修改ApplicationContext.xml配置文件,增加两行:

<prop key="batchImport">PROPAGATION_REQUIRED</prop>

 <prop key="batchDelete">PROPAGATION_REQUIRED</prop>

这样就去掉了,readOnly模式(FlushMode.NEVER)。即改为一个为非readOnly的事务。

b)      方法二、在代码级别上,直接的进行设置。它将覆盖applicationContext.xml中的配置。因为代码级别上,可以直接的控制最终的处理方式。

2. 进一步的修改:因为默认设置为:FLUSH_NEVER。告诉Hibernate避免和数据库进行不必要的对象同步,将所有的更新延迟到事务的结束。

a)      尝试的手工刷新内存:然后修改:在BatchProcessDao中的,批量导入中,Create(PO),后添加了:getSession().flush();进行了清理缓存。

6.1.1             阶段性测试

在debug模式下,开始调试,处理完一条数据后,没有发现这条合法的数据。

l         原因:整个的操作过程中,都是在一个事务的管理下进行的。根据事务具有原子性原理可知:Hibernate提交事务意味着对数据库所做的更新被永久保存下来。 每处理完文件中的一条正确的数据时,并没有入库,而是当全部处理完所有的文件中数据时,才入库。 这必将造成,调用webservice接口失败。

l         解决思路:这说明每处理完一条数据,必须强制的入库。需要进一步的修改。

6.2     进一步修改:添加强制的提交机制

6.2.1             修改方法和步骤

l         在BatchProcessDao类中,处理一条数据时,在Code级别上,强制启动一个新事务,来完成单条数据的及时的入库。

6.2.2             详细代码

6.3    最终测试结果

可以正常的运行。,处理一条数据时,及时的提交到DB,也保证了调用webservice接口成功。

6.4    解决方法评估

优点

缺点

1.不用修改spring的自动生成配置文件的模板机制。

 

2.修改的代码量很小。

在配置文件中,已经声明了对批量操作是有事务的,然后到处理到文件中,每一行的数据时,又启动了一个新的事务。可能存在安全隐患。例如:引起数据的死锁、涉及到事务的回滚。

7       解决办法二:批量操作配置为非事务模式和分拆Service类

7.1    修改过程

7.1.1            修改配置文件中的事务

1. 把支持批量操作的service类,在配置文件中配置为:非事务的(no-transactional)的形式。

前后的比较:

图一、支持事务配置

图二、非事务的配置

7.1.2            修改Session的刷新模式的方法和步骤

7.1.2.1             修改Spring刷新模式方法
7.1.2.1.1       在BatchProcessDao中添加修改内存刷新模式代码

直接的在BatchProcessDao中的batchImport中增加相关的代码,修改相关的Session的刷新模式为FlushMode.AUTO,

 原因是:Spring默认情况下,是FulshMode.NEVER,(可以参见OpenSessionInViewFilter类,下面将进行详细描述),而在读取文件中行数据时,或在记录日志时,需要创建PO入库。在FulshMode.NEVER模式下是不允许的。

因此要在代码级别上,设置Session刷新模式(FlushMode)为FlushMode.AUTO,配置文件中的刷新模式将自动失效。实例代码如图:

7.1.2.1.2        修改MyOpenSessionInViewFilter类来设置刷新模式

MyOpenSessionInViewFilter类是继承来自Spring的OpenSessionInViewFilter类。默认设置下:Session的刷新模式为:FlushMode.NEVER。如果修改刷新模式。需要重载OpenSessionInViewFilter的getSession方法把Session的刷新模式修改为:FlushMode.AUTO,

 

 

7.1.2.1.2.1  在web.xml中的Filter的配置

7.1.2.1.2.2  修改后产生的后果

在系统初始化时,就已经把Session的刷新模式设置为了:FlushMode.AUTO(默认情况下是:FlushMode.NEVER)。然而,我们在spring的配置文件applicationContext.xml对txProxyTemplate的设置为如图:

其中

这样设置的本意是:让一些非指定的一些方法或方法集外,都是只读事务。因为现在如果在MyOpenSessionInViewFilter中设置刷新模式为:FlushMode.AUTO,这个文件中这个对方法的一些事务上约束,将变的无效。在Service中所有的函数,都将变为支持FlushMode.AUTO的事务。显然在MyOpenSessionInViewFilter的设置是不合适的。

结论:在 MyOpenSessionInViewFilter修改刷新模式为FlushMode.AUTO不合适。不能这么修改。

7.2    方法评估

优点

缺点

1.由于配置为非事务的处理机制。这样在批量操作数据时,能够使得在文件中得到的合法数据及时的入库,也能及时的调用其他webservice接口。

2.功能分类比较清晰。把批量操作单独放到一个Service类。扩展性比较好。

1. 修改Spring自动生成配置文件的模板,添加进去相关的非事务的service配置。

8       结论

1. Spring启动时,默认把hibernate的刷新模型FlushMode设置为:Never

2. 对于批量操作,对于每一条数据处理,如果调用webservice接口才能完成时,把此操作配置为非事务的。

3. 可以通过spring的代码来启动和关闭一个事务,它将覆盖配置文件中的事务配置。

4. 对于批量操作,步骤性很想,应用Template Method Pattern可以带来很高的生产力。

9       感谢

感谢集体智慧。

10            参考资源

1.《精通Hibernate:Java对象持久化技术详解》177页

2.Spring的事务管理