`
can4you
  • 浏览: 101800 次
  • 性别: Icon_minigender_1
  • 来自: 天朝
社区版块
存档分类
最新评论

Spring 声明式事务中进行多线程操作及解析

阅读更多

今天项目还存在一个问题,而明天就要上线了,所以很急,主要的问题如下:

 

问题描述:主要的业务逻辑--页面上处理放行基金下单记录(主管审核以后的操作),这时候需要做的动作如下:

 

1. 把下单记录关联的档案移动到MFT(TIBCO公司产品,文档传送软件)的发送目录;

2. 更新下单记录信息(OrderInfo)的状态;

3. Daemon(JAVA注册的windows service)程序去检测MFT发送目录是否有档案,有则起一个新的Thread调用MFT Server去发送档案,MFT会自动发送到目标目录,发送成功或失败会有一个回覆信息,AP端根据这个状态去更新下单记录(OrderInfo)的状态;

 

处理以上业务逻辑的service类使用了Spring声明式事务进行管理,然后客户在测试机上反馈的信息是:

1. 抛出一下异常信息

 INFO [WebContainer : 4] [2010-10-25 17:17:01,359] (com.service.impl.OrderCenterImpl) - Release Order! [uniqueNo:94101025C,orderNo:1]
 WARN [WebContainer : 4] [2010-10-25 17:17:03,531] (org.hibernate.util.JDBCExceptionReporter) - SQL Error: 1222, SQLState: S00051
ERROR [WebContainer : 4] [2010-10-25 17:17:03,531] (org.hibernate.util.JDBCExceptionReporter) - 鎖定要求的逾時期間已過。
ERROR [WebContainer : 4] [2010-10-25 17:17:03,546] (com.service.impl.FunctionActionImpl) - Hibernate operation: could not execute update query; bad SQL grammar [update SF_OrderMessages set status=? where TransactionNo=? and msgLevel=?]; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: 鎖定要求的逾時期間已過。
org.springframework.jdbc.BadSqlGrammarException: Hibernate operation: could not execute update query; bad SQL grammar [update OrderInfo set status=? where id=?]; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: 鎖定要求的逾時期間已過。
com.microsoft.sqlserver.jdbc.SQLServerException: 鎖定要求的逾時期間已過。
	at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(Unknown Source)
	at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(Unknown Source)
	at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(Unknown Source)
	at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(Unknown Source)

 

 WARN [WebContainer : 4] [2010-10-25 17:17:03,859] (org.hibernate.util.JDBCExceptionReporter) - SQL Error: 1205, SQLState: 40001
org.springframework.dao.DeadlockLoserDataAccessException: Hibernate operation: could not execute update query; SQL [update OrderInfo set status=? where id=?]; 交易 (處理序識別碼 134) 在 鎖定 資源上被另一個處理序鎖死並已被選擇作為死結的犧牲者。請重新執行該交易。; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: 交易 (處理序識別碼 134) 在 鎖定 資源上被另一個處理序鎖死並已被選擇作為死結的犧牲者。請重新執行該交易。
com.microsoft.sqlserver.jdbc.SQLServerException: 交易 (處理序識別碼 134) 在 鎖定 資源上被另一個處理序鎖死並已被選擇作為死結的犧牲者。請重新執行該交易。
	at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(Unknown Source)
	at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(Unknown Source)
	at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(Unknown Source)
	at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(Unknown Source)
	at com.microsoft.sqlserver.jdbc.TDSCommand.execute(Unknown Source)

 

2. MFT的档案没有传送到指定的目录,即传送失败

 

分析原因:

1. 在声明式事务里使用多线程去更新OrderInfo表(主要原因)

2. Daemon也去更新OrderInfo表

 

MFTSenderServiceImpl.java在Spring里配置了声明式事务管理,其transfer方法中迭代调用transferFile方法:

	public void transfer() throws Exception {
		
		.....
		.....
		
		for (int i = 0; i < movedFiles.size(); i++) {
			transferFile(movedFiles.get(i));
		}
	}

  

	private void transferFile(final String fileName) throws Exception {
		...
		...
		mftSenter.sendFiles(faxFilePaths.toArray(new String[] {}), new SenderCallback() {

			public void failed() {
				...
				//update OrderInfo operation
			}

			public void success() {
				...
				//update OrderInfo operation
                 }
		});
		...
		...
	}

 

	public void sendFiles(String[] files, SenderCallback callBack) {
		sendMFTFile(faxAgentName, faxAgentDir, files, mftCLIcmd, maxRetry, callBack);
	}

	public static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS,
			new ArrayBlockingQueue<Runnable>(50), new RejectedExecutionHandler() {

				public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {
					Thread thread = new Thread() {
						public void run() {
							try {
								Thread.sleep(1000);
							} catch (InterruptedException e) {
							}
							logger.debug(r.toString() + ": readd current thread to threadPool");
							executor.execute(r);
						};
					};
					thread.start();
				}
			});

	protected void sendMFTFile(String tdccAgentName, String tdccAgentDir, String[] pathes, String mftCLIcmd,
			int maxRetry, final SenderCallback callback) {
			...
			...
			MFTSendThread sendThread = new MFTSendThread(tdccAgentName, tdccAgentDir, newPathes
					.toArray(new String[] {}), mftCLIcmd, maxRetry, callback);
			threadPool.execute(sendThread);
	}

 

用到了JDK的并发处理ThreadPoolExecutor,并提供了自定义的RejectedExecutionHandler的处理,关于ThreadPoolExecutor的几个参数的解释如下:

1. corePoolSize: 线程池中保持的核心线程数(可以认为是最少线程数)

2. maximumPoolSize: 线程池最大线程数

3. keepAliveTime: 某线程idle时间超过该时间,则自动terminate

4. unit: 时间单位

5. workQueue: 线程池的工作队列

6. handler: 如果线程池中的线程数超过了workQueue的容量(当然肯定也超过core的容量)进行的后续处理

 

解析: 当线程池建立以后,如果执行execute(Runable r);方法添加一个线程时,根据以下情况进行处理:

(1) 如果curSize<corePoolSize,即使池中存在idle状态的线程,也创建一个线程补充到池中;

(2) 如果curSize=corePoolSize,且workQueue未满,则加入到workQueue中;

(3) 如果curSize>corePoolSize,而workQueue已满且curSize<maximumPoolSize,创建一个线程加入到池中;

(4) 如果curSize>corePoolSize,而workQueue已满且curSize>=maximumPoolSize,使用handler处理,默认JDK对handler提供了几种实现,根据实际要求选择或者自行实现RejectedExecutionHandler类的rejectedExecution方法。

 

curSize: 当前线程池的数量

 

那么:就出现了事务中使用多线程去更新DB中某个table中的多条记录的情况,造成死锁的情况就很容易出现了。

 

<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
	<bean id="faxCenterImpl" class="com..service.impl.FaxCenterImpl">
		....
	</bean>

	<bean id="faxCenter" parent="transProxy">
		<property name="target" ref="faxCenterImpl" />
	</bean>
</beans>

 

最后的解决方式是,Daemon程序没有采用事务处理的bean实例faxCenter,而是使用faxCenterImpl的实例,该实例没有纳入Spring声明式事务管理,而其他地方用到的faxCenter依然是有事务管理的实例对象。

 

public class FaxTransferProcess extends BaseDaemonInSpringContext {

	private static final Log logger = LogFactory.getLog(FaxTransferProcess.class);

	private IFaxCenter faxCenter;

	public void init(ApplicationContext context) {
		faxCenter = (IFaxCenter) context.getBean("faxCenterImpl");//faxCenterImpl而不是faxCenter
	}

	public void process() {
		try {
			faxCenter.transfer();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}
}

 

这样保证了在事务里不会去多线程更新记录是比较好的解决方式,看网上一些讨论的做法是利用hibernate的锁机制来处理多线程的问题,我没有去试过,也不知道好不好。但是至少这个问题是解决了,以后有空再去研究一下事务中并发处理的问题。

 

【参考文章】

1. http://mysaga.iteye.com/blog/436718

2. http://www.iteye.com/topic/515634

分享到:
评论
4 楼 can4you 2011-05-20  
youjianbo_han_87 写道
对同一个表做update操作时,多线程能起作用吗? update操作应该会有行级锁把。。。


我这里的解决方式是把更新数据的processor类不纳入事务管理。
引用
  
<bean id="faxCenterImpl" class="com..service.impl.FaxCenterImpl"> 
    ....  
</bean> 
<bean id="faxCenter" parent="transProxy"> 
   <property name="target" ref="faxCenterImpl" /> 
</bean> 

这里faxCenter是纳入了事务管理的,但是我在processor中取的bean的时候使用:
引用

faxCenter = (IFaxCenter) context.getBean("faxCenterImpl");//faxCenterImpl而不是faxCenter


我的理解是,存在事务,然后多线程更新,在事务提交时,很容易造成死锁(就是因为存在行级锁)。
现在不纳入事务管理,每个线程去更新数据后立刻提交,造成死锁的概率下降了。这里只是折中的解决方式。
3 楼 youjianbo_han_87 2011-05-04  
对同一个表做update操作时,多线程能起作用吗? update操作应该会有行级锁把。。。
2 楼 can4you 2010-12-22  
哈哈,是啊,这都被你发现啦。。。。
1 楼 gelnyang 2010-12-21  
Iain?

相关推荐

    多线程及spring相关面试专题及答案.zip

    6. **Spring事务管理**:Spring提供了声明式和编程式的事务管理,确保在多线程环境中数据的一致性。 7. **Spring Bean**:Spring容器管理的对象称为Bean,容器通过XML、注解或Java配置来定义Bean的生命周期和装配...

    java 后端学习资料包含(spring,多线程).zip

    此外,Spring框架还提供了`@Transactional`注解,用于声明式事务管理,这在多线程环境中尤为重要,因为正确地管理事务边界是确保数据一致性的关键。 本资料包中可能包含有关Spring框架的源码解析,帮助开发者理解其...

    Spring事务详解

    Spring声明式事务主要依赖于AOP(面向切面编程)实现,它通过分析事务注解或XML配置,动态地织入事务管理代码。在声明式事务中,事务属性是非常关键的,它们决定了事务的行为。这些属性包括: 1. 事务的传播行为:...

    Spring多数据源分布式事务管理

    最后,通过@TransactionManagement注解开启Spring的声明式事务管理,并使用@Transactional注解来指定事务边界。 接下来,我们要关注的是Druid和MyBatis在多数据源环境中的应用。Druid是一个优秀的数据库连接池,它...

    S详细讲解SH中Spring事务流程.doc

    在开发过程中,理解Spring事务管理的工作原理和配置方式至关重要,因为事务管理是保证数据完整性的基石,尤其在多线程和并发环境下,正确地管理事务对于防止数据不一致至关重要。通过合理的配置和使用,我们可以确保...

    spring事务全解释

    1. **声明式事务管理**:这是Spring最常用的方式。通过在配置文件或者使用注解(如`@Transactional`)来定义事务的边界。当方法被调用时,Spring会自动开始事务,执行完毕后根据结果决定提交或回滚事务。这种方式...

    Spring系列面试题129道(附答案解析)

    - 事务管理:Spring的事务管理抽象为不同事务API提供了简单而强大的声明式事务管理。 - 集成了其他框架和标准:Spring与流行的ORM框架、JDBC、JMS、JPA等都有良好的集成。 - 支持测试:Spring对单元测试和集成测试都...

    spring3.1完整包

    9. **org.springframework.transaction-3.1.0.M1.jar**:事务管理模块提供了编程和声明式事务管理的实现,支持各种事务API,如JTA、JDBC、Hibernate等。3.1版本改进了事务恢复机制和异步事务支持。 10. **org....

    mybatis-spring-1.3.1

    Spring提供了声明式事务管理,可以将事务管理配置在Service层的方法上。mybatis-spring会自动处理事务的开启、提交、回滚等操作,开发者无需在代码中显式处理。 六、总结 mybatis-spring-1.3.1作为Spring与MyBatis...

    spring-framework-3.2.18.RELEASE

    - **声明式事务管理**:Spring 3.2.18.RELEASE提供了基于XML和注解的两种声明式事务管理方式,简化了事务处理代码。 2. **核心模块** - **Spring Core Container**:包含Bean工厂和ApplicationContext,是Spring...

    spring4.2的lib包

    4. **AOP(面向切面编程)**:Spring支持声明式事务管理、日志记录、安全控制等多种跨切面关注点,使得代码更加模块化和可维护。 5. **Asynchronous Processing**:Spring 4.2版本进一步增强了异步处理能力,开发者...

    精通spring(part 4)共分5个part (罗时飞)

    Spring提供了一套完整的事务管理方案,包括声明式事务管理和编程式事务管理。Spring事务管理基于AOP实现,可以轻松地与Spring框架中的其他特性集成。 #### 4.2 声明式事务管理 通过在配置文件中使用XML或通过注解的...

    spring-kafka源代码

    它通过Spring的声明式编程模型,使得开发者可以轻松地在应用中添加消息传递功能。在源代码层面,Spring Kafka主要由以下几个模块组成: 1. `org.springframework.kafka.core`:这是Spring Kafka的核心模块,包含...

    spring.net中文手册在线版

    14.5.1.理解Spring.NET声明式事务管理的实现 14.5.2.第一个例子 14.5.3.Transaction特性的设置 14.5.4.通过AutoProxyCreator使用声明式事务 14.5.5.通过TransactionProxyFactoryObject使用声明式事务 14.5.6. 通过...

    Struts2 hibernate spring

    2. **事务管理**:Spring提供了声明式事务管理,可以在XML配置文件或注解中定义事务边界,简化了事务处理的复杂性。 3. **AOP集成**:Spring的AOP功能可以用于日志记录、性能监控、安全控制等,例如,可以在Struts2...

    Spring+SpringMVC基础学习笔记(b站课程学习笔记)

    声明式事务管理通过在配置文件或注解中声明事务边界,而编程式事务管理则是在代码中显式调用开始、提交、回滚等事务操作。Spring的事务管理支持多种事务API,如JDBC的Connection、JTA(Java Transaction API)等。 ...

    spring4+hibernate4+security4整合

    此外,还需要配置Hibernate的SessionFactory,使其与Spring的事务管理器配合工作,实现声明式事务管理。 2. **整合Hibernate**:通过Spring的HibernateTemplate或SessionFactoryBean,可以在Spring容器中管理...

    spring integration in action

    - **Spring Integration 概览:** Spring Integration 是一个基于 Spring 框架的企业集成解决方案,它提供了一种声明式的、面向消息的方式来构建集成应用程序。 - **组件介绍:** Spring Integration 包括消息通道...

    Spring AOP源码笔记

    - **事务管理**: 自动管理数据库事务,如使用`@Transactional`注解进行声明式事务控制。 **4. Spring AOP的源码分析** - **AOP代理创建**: Spring如何创建JDK或CGLIB代理对象。 - **通知执行流程**: 当一个方法被...

    官方原版源码 spring-framework-5.2.9.RELEASE.zip

    5. **事务管理**:Spring的PlatformTransactionManager接口和不同的事务策略(如DataSourceTransactionManager)在源码中展示了如何实现声明式事务管理。 三、源码学习价值 1. **设计模式实践**:Spring源码中大量...

Global site tag (gtag.js) - Google Analytics