2018.8.2 java电商从1到2--chapter12 SpringSchedule实现定时关单

目录

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

有结果集:表锁。无结果集:不加锁。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值