手动控制spring事务

    用spring的时候,什么时候需要手动控制事务?在方法前加@Transaction不行么?

    有这么一种场景,用户在web页面点击一个按钮,后台需要几分钟或几小时的长时间执行一段程序。这时如果页面不响应,用户感觉就一直卡在这里了。一种方法是起一个线程来跑这个程序,让请求立即返回。但这与我们平时的代码不太一样。平时的代码如果有事务直接放在Service的方法上就行了,再这个真正的执行是在一个线程里。而这里得不到外面的开的事务。所以就在执行方法里手动打开与关闭事务。

   关于长时任务,在这篇文章中有介绍 《Java耗时任务怎么不重复执行》。这里就不多说了,看以下代码,根据经验1000行以下就直接执行,以上就开起一个线程跑数据:

@Transactional
public String processDatas(ImportVo importVo,List<Map<String,String>> data) {
	String rstInfo=null;
	WorkState startState=new WorkState(WorkState.STATE.DOING.getCode(),new Date(),null);
	setWorkSate(importVo.getServiceId(),importVo.getGradeId(),JSONObject.toJSONString(startState));
	if(data.size()<1000	) {
		try {
			int sucNum=importAnswers(importVo);
			rstInfo="成功导入"+sucNum+"份数据";
		}catch(Exception e) {
			log.error(e.getMessage(),e);
			rstInfo=e.getMessage();
			setWorkSate(importVo.getServiceId(),importVo.getGradeId(),JSONObject.toJSONString(new WorkState(WorkState.STATE.FAIL.getCode(),new Date(),e.getMessage())));
		}
	}else {
		
		String key="TaskKey";
		LongTimeWorkThread ltw=new LongTimeWorkThread() {
			
			@Override
			public void doWork() {
				try {
					int sucNum=importAnswers(importVo);
				}catch(Exception e) {
					log.error(e.getMessage(),e);
					setWorkSate(importVo.getServiceId(),importVo.getGradeId(),JSONObject.toJSONString(new WorkState(WorkState.STATE.FAIL.getCode(),new Date(),e.getMessage())));
				}
			}
			@Override
			public Object finish() {
				distributeWorkService.clearKey(key);
				return null;
			}
		};
		StartInfo startInfo=distributeWorkService.checkStart(key, ltw);
		if(startInfo.isThisStartFlag()) {
			rstInfo="启动成功,预计需要:"+data.size()/10+"秒"; 
		}else {
			SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			rstInfo="启动失败,现在执行的是"+sdf.format(startInfo.getStartTime())+"开始的任务";
		}
	}
	return rstInfo;
	
}

setWorkSate方法是设置这个任务的状态,方便用户了解任务的执行情况,前面两个是业务键,作为任务的主键。表结构如下:

CREATE TABLE `import_work` (
  `id` bigint(20) NOT NULL,
  `activity_id` bigint(20) DEFAULT NULL,
  `grade_id` int(11) DEFAULT NULL,
  `msg` varchar(512) DEFAULT NULL COMMENT '成功,失败信息',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

这个processDatas也是有事务的。但在线程中的importAnswers方法就没有事务了。可以这样手动控制事务:

1.注入
@Autowired
private DataSourceTransactionManager dstManager

2.开启事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
TransactionStatus transaction= dstManager.getTransaction(def); // 获得事务状态

3.提交事务
dstManager.commit(transaction);

4.回滚事务
dstManager.rollback(transaction);

我的项目中对数据源重新配置过了,配置方法见http://www.highersoft.net/html/notice/notice_447.html,所以第1步注入有问题。所以我的注入方法是这样的:

//DynamicDataSourceConfiguration是数据源配置类 @Configuration

@Autowired

private DynamicDataSourceConfiguration dynamicDataSourceConfiguration;

开启事务时,这样取事务管理对象:

PlatformTransactionManager dataSourceTransactionManager=dynamicDataSourceConfiguration.platformTransactionManager();

其它的一样。

如果在这个手工事务里想做个数据库操作让用户立刻可以看见,但又不想影响之前开的事务(之前开的事务未提交,不能让用户看见)。可以在方法中间再加一个方法,再开一个手工事务,代码同上,就可以了。


上面记录得有点乱,最近又遇到这个问题。对于大文件的导入还是用手工事务的方式,不过这次用简化的方法。直接取spring管理的org.springframework.jdbc.datasource.DataSourceTransactionManager,代码模板是这样。

@Autowired
private DataSourceTransactionManager transactionManager;
public String importFile(String fileName,Integer rewriteType)  {
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
    TransactionStatus transaction = transactionManager.getTransaction(def); // 获得事务状态
    try{
        //业务处理

        transactionManager.commit(transaction);
    }catch(Exception e) {
        transactionManager.rollback(transaction);
        log.error(e.getMessage(),e);
    }
    return "成功导入";
}
当然,业务处理这块可能比较麻烦,Exception处理也有些技巧。文件导入批量更新可参考 《批量插入,用mybatis还是jdbc》 


文/程忠 浏览次数:0次   2019-06-12 20:51:50

相关阅读

微信扫描-捐赠支持
加入QQ群-技术交流

评论:
点击刷新

↓ 广告开始-头部带绿为生活 ↓
↑ 广告结束-尾部支持多点击 ↑