目录
chapter12 SpringSchedule实现定时关单
12.1 总览
12.2 cron表达式
12.3 Spring Schedule的使用
12.3.1 配置文件applicationContext.xml
12.3.2 @Schedule注解
12.3.3 真正的关单操作
12.4 mysql的行锁和表锁
12.1 总览
- SpringSchedule的使用
- cron表达式
- mysql的表锁与行锁
12.2 cron表达式
(1)格式
秒 分 时 日 月 周 年(可选)
详细的见文档。
(2)常用表达式
0 */1 * * * ?
注意这里*/1表示每个一分钟,不是每隔一分钟。比如启动时是12:20:29,那么第一次定时任务执行的时间不是一分钟后,而是12:21。
(3)cron表达式生成器
直接百度生成器即可。
12.3 Spring Schedule的使用
12.3.1 配置文件applicationContext.xml
注意别导入错Xmlns等参数,因为有狠毒<task>标签。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
<context:component-scan base-package="com.mmall" annotation-config="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<aop:aspectj-autoproxy/>
<!-- 二期新增spring schedule的时候新增的 -->
<context:property-placeholder location="classpath:datasource.properties"/>
<task:annotation-driven/>
<import resource="applicationContext-spring-session.xml"/>
<import resource="applicationContext-datasource.xml"/>
</beans>
12.3.2 @Schedule注解
这里方法命名为closeOrderTaskV1,是因为后续还要改进,这里显然没考虑到集群场景。集群时不需要每台服务器都执行定时任务。
@Component
@Slf4j
public class CloseOrderTask {
@Autowired
private IOrderService iOrderService;
@Scheduled(cron="0 */1 * * * ?")//每1分钟(每个1分钟的整数倍)
public void closeOrderTaskV1(){
log.info("关闭订单定时任务启动");
int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour","2"));
iOrderService.closeOrder(hour);
log.info("关闭订单定时任务结束");
}
}
12.3.3 真正的关单操作
这里提到的一定要用where主键防止锁表将在下节详细说明。
@Service("iOrderService")
@Slf4j
public class OrderServiceImpl implements IOrderService {
@Override
public void closeOrder(int hour) {
Date closeDateTime = DateUtils.addHours(new Date(),-hour);
List<Order> orderList = orderMapper.selectOrderStatusByCreateTime(Const.OrderStatusEnum.NO_PAY.getCode(),DateTimeUtil.dateToStr(closeDateTime));
for(Order order : orderList){
List<OrderItem> orderItemList = orderItemMapper.getByOrderNo(order.getOrderNo());
for(OrderItem orderItem : orderItemList){
//一定要用主键where条件,防止锁表。同时必须是支持MySQL的InnoDB。
Integer stock = productMapper.selectStockByProductId(orderItem.getProductId());
//考虑到已生成的订单里的商品,被删除的情况
if(stock == null){
continue;
}
Product product = new Product();
product.setId(orderItem.getProductId());
product.setStock(stock+orderItem.getQuantity());
productMapper.updateByPrimaryKeySelective(product);
}
orderMapper.closeOrderByOrderId(order.getId());
log.info("关闭订单OrderNo:{}",order.getOrderNo());
}
}
}
public interface ProductMapper {
//这里一定要用Integer,因为int无法为NULL,考虑到很多商品已经删除的情况。
Integer selectStockByProductId(Integer id);
}
<mapper namespace="com.mmall.dao.ProductMapper" >
<select id="selectStockByProductId" resultType="int" parameterType="java.lang.Integer">
select
stock
from mmall_product
where id = #{id}
for update
</select>
</mapper>
12.4 mysql的行锁和表锁
上文的sql语句中使用了select ... for update。这是为了加锁,防止刚刚查询出来的结果是过时的值(中间又有其他线程修改这个记录),后续进行的set语句使用过时的值处理,会覆盖中间的修改。
注意:使用mysql的select ... for update需要使用InnoDB引擎。
- 当查询条件中明确指定主键时,是行锁。
- 当查询条件中不明确指定主键时,是表锁。
- 如果查询结果为空,不加锁。
示例:
select * from user where id=#{id} for update
有结果集:行锁。无结果集:不加锁。
select * from user where name=#{name} for update
有结果集:表锁。无结果集:不加锁。
select * from user where id<>#{id} for update
有结果集:表锁。无结果集:不加锁。