1. 优享JAVA首页
  2. 默认分类

那些年,我们见过的 Java 服务端“问题”

采用分页查询

“采用分页查询”是指定 startIndex(开始序号)和 pageSize(页面大小)进行数据查询,或者指定 pageIndex(分页序号)和 pageSize(页面大小)进行数据查询。例子代码如下:

/** 订单DAO接口 */
public interface OrderDAO {
    /** 统计过期订单函数 */
    @Select("select count(*) from t_order where status = 5 and gmt_create < date_sub(current_timestamp, interval 30 day)")
    public Long countTimeout();
    /** 查询过期订单函数 */
    @Select("select * from t_order where status = 5 and gmt_create < date_sub(current_timestamp, interval 30 day) limit #{startIndex}, #{pageSize}")
    public List<OrderDO> queryTimeout(@Param("startIndex") Long startIndex, @Param("pageSize") Integer pageSize);
}

/** 订单服务接口 */
public interface OrderService {
    /** 查询过期订单函数 */
    public PageData<OrderVO> queryTimeout(Long startIndex, Integer pageSize);
}

适用于真正的分页查询,查询参数 startIndex(开始序号)和 pageSize(页面大小)可由调用方指定。

分页查询隐藏问题

假设,我们需要在一个定时作业(每 5 分钟执行一次)中,针对已经超时的订单(status=5,创建时间超时 30 天)进行超时关闭(status=10)。实现代码如下:

/** 订单DAO接口 */
public interface OrderDAO {
    /** 查询过期订单函数 */
    @Select("select * from t_order where status = 5 and gmt_create < date_sub(current_timestamp, interval 30 day) limit #{startIndex}, #{pageSize}")
    public List<OrderDO> queryTimeout(@Param("startIndex") Long startIndex, @Param("pageSize") Integer pageSize);
    /** 设置订单超时关闭 */
    @Update("update t_order set status = 10 where id = #{orderId} and status = 5")
    public Long setTimeoutClosed(@Param("orderId") Long orderId)
}

/** 关闭过期订单作业类 */
public class CloseTimeoutOrderJob extends Job {
    /** 分页数量 */
    private static final int PAGE_COUNT = 100;
    /** 分页大小 */
    private static final int PAGE_SIZE = 1000;
    /** 作业执行函数 */
    @Override
    public void execute() {
        for (int i = 0; i < PAGE_COUNT; i++) {
            // 查询处理订单
            List<OrderDO> orderList = orderDAO.queryTimeout(i * PAGE_COUNT, PAGE_SIZE);
            if (OrderDO order : orderList) {
                // 进行超时关闭
                ......
                orderDAO.setTimeoutClosed(order.getId());
            }

            // 检查处理完毕
            if(orderList.size() < PAGE_SIZE) {
                break;
            }
        }
    }
}

粗看这段代码是没有问题的,尝试循环 100 次,每次取 1000 条过期订单,进行订单超时关闭操作,直到没有订单或达到 100 次为止。但是,如果结合订单状态一起看,就会发现从第二次查询开始,每次会忽略掉前 startIndex(开始序号)条应该处理的过期订单。这就是分页查询存在的隐藏问题:

当满足查询条件的数据,在操作中不再满足查询条件时,会导致后续分页查询中前 startIndex(开始序号)条满足条件的数据被跳过。

可以采用”设置最大数量”的方式解决,代码如下:

/** 订单DAO接口 */
public interface OrderDAO {
    /** 查询过期订单函数 */
    @Select("select * from t_order where status = 5 and gmt_create < date_sub(current_timestamp, interval 30 day) limit 0, #{maxCount}")
    public List<OrderDO> queryTimeout(@Param("maxCount") Integer maxCount);
    /** 设置订单超时关闭 */
    @Update("update t_order set status = 10 where id = #{orderId} and status = 5")
    public Long setTimeoutClosed(@Param("orderId") Long orderId)
}

/** 关闭过期订单作业(定时作业) */
public class CloseTimeoutOrderJob extends Job {
    /** 分页数量 */
    private static final int PAGE_COUNT = 100;
    /** 分页大小 */
    private static final int PAGE_SIZE = 1000;
    /** 作业执行函数 */
    @Override
    public void execute() {
        for (int i = 0; i < PAGE_COUNT; i++) {
            // 查询处理订单
            List<OrderDO> orderList = orderDAO.queryTimeout(PAGE_SIZE);
            if (OrderDO order : orderList) {
                // 进行超时关闭
                ......
                orderDAO.setTimeoutClosed(order.getId());
            }

            // 检查处理完毕
            if(orderList.size() < PAGE_SIZE) {
                break;
            }
        }
    }
}

本文来自阿里巴巴中间件:常意,经授权后发布,本文观点不代表优享JAVA立场,转载请联系原作者。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

QR code