不知道大家在实际开发的过程中,有没有遇到过类似的情况:比如有这么一个定时任务,需要定时处理一些超时未付款的订单。然后你查询所有符合条件的记录(假设有10条记录),接着for循环遍历每一个订单,每个循环体都要执行大量的业务,比如商品库存回退,订单状态修改,业务流水添加,消息推送等。
你前五个循环都执行成功了,可是执行第六个循环的时候突然抛出一个异常,导致你前面五个执行成功的订单都事务回滚了,就有种三国的赤壁之战,曹军铁索连舟,一荣俱荣,一损俱损的感觉,是不是很蛋疼?你还真别笑,我接手公司项目的时候,还真的就看到前面的开发这样写代码。没办法,既然现在是咱接手,还就得老老实实的填坑了。
解决方案其实很简单,只需要一两段代码就能解决,本人亲测有效,当时测试的方式是:查询的10条记录,设置第6条出现异常,然后在10个循环都结束完后,发现只有第6条记录保持原样(异常时事务回滚),而它前面的5条和后面的4条都正常执行,并没有受影响。
我的项目用的是ssm框架,使用spring自带的定时任务,下面开始上伪代码:
先是OrderTimer.java类:该类是获取所有符合要求的记录,也就是获取这个集合,然后开始遍历执行每条符合要求的记录,注意具体执行的业务要封装到另一个方法中,也就是封装到业务层处理,不单单只是因为代码规范,更主要的是因为封装在另一个方法,才能更随心所欲的设置事务的传播属性。
package demo;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class OrderTimer {
private static Logger logger = Logger.getLogger(OrderTimer.class);
@Autowired
private OrderService orderService;
@Autowired
private OrderMapper orderMapper;
/**
* 堆代码 duidaima.com
* 每隔1分钟执行未付款订单的关闭的操作
*/
@Scheduled(cron = "0 0/1 * * * ?")
public void closeOrders() {
logger.info("--------开始每隔1分钟执行未付款订单的关闭的操作");
//获取所有超时未付款的订单,这个需要根据你自己的实际情况编写,此处只是举例
List<Order> list= orderMapper.listCloseOrder();
if(list==null||list.size()<1){
return ;
}
for (Order order : list) {
orderService.closeOrder(order);
}
}
}
下面这个是实现层的代码,重点留意两个“关注点”:
package demo;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class OrderServiceImpl implements OrderService {
private static Logger logger=Logger.getLogger(OrderServiceImpl.class);
@Autowired
private OrderMapper orderMapper;
/**
这个注解很重要,意思是设置当前方法的事务传播级别为REQUIRES_NEW,表示当前方法内的所有事务都是独立的,不影响整体的事务。
有的项目使用注解的方式配置当前方法传播属性会无效,此时可能需要你去你的spring-mybatis.xml文件中配置,效果是一样的
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)//关注点一!!!
@Override
public void closeOrder(Order order) {
try{
//这里,执行你自己的业务,比如商品库存回退,订单状态改变,操作流水等。
//重点关注的是@Transactional注解和TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
}catch (Exception e){
logger.info("网络异常:"+e.getMessage());
//这一段表示手动回滚事务,此处的try-catch,是表示当前方法如果出错了,
//那我就自己消化这个异常,不再往外抛,处理异常的方式是手动回滚事务。
//如此,每个循环体都自己处理自己的事务,不管成功与失败,都不影响整个循环
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//关注点二!!!
}
}
}
大致的逻辑便是如此,这种东西无法分享具体的代码,因为涉及到事务就得有数据库操作,有数据库操作就得建库建表,不可能全部写出来,那样不管对写的人还是看的人都很难受,所以不如省略具体的核心业务,只关注几个需要关注的点,然后以你现有的项目为基础实现出来并亲自测试一下。
当然,既然是伪代码,就需要有一定代码量的人才能脑补啊。