You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot主座位表数据意外更新问题排查求助

问题

编写Java代码用于检查座位可用性状态并保存预订数据,但执行检查流程后发现主座位表(master seat table)中的数据被意外更新,而代码本不应修改该表。尝试过将数据转为字符串再解析为JSON避免修改(有效但资源消耗大),现寻求低资源消耗的解决方案。

代码片段

获取分组座位数据方法

public List<CheckInSeatResponse> getGroupSeatData(Long serviceId, Date departDate, String departGroup, String destinationGroup, Long busId) {
    try {
        List<CheckInSeatResponse> manifestData = manifestPassengerRepository.getGroupSeatData( departDate, departGroup, destinationGroup, busId, serviceId);
        return manifestData;
    } catch (Exception e) {
        log.error(e.getMessage());
        return null;
    }
}

获取分组座位映射方法

public List<Seat> getGroupSeatMap(Long serviceId, Date departDate, String departGroup, String destinationGroup, Long busId) {
    try {
        List<CheckInSeatResponse> manifestData = getGroupSeatData( serviceId, departDate, departGroup, destinationGroup, busId);
        List<Seat> seatData = seatBusRepository.findByBusIdOrderByIdAsc(busId);
        List<Seat> seatMap = new ArrayList<>();
        if (manifestData != null) {
            for (Seat seat : seatData) {
                Seat newSeat = seat;
                for (CheckInSeatResponse seatCheck : manifestData) {
                    if (seatCheck.getSeatNumber() != null && seatCheck.getSeatNumber().equals(seat.getSeatNumber())) {
                        newSeat.setSeatStatus("notAvailable");
                    }
                }
                seatMap.add(newSeat);
            }
            return seatMap;
        } else {
            return seatData;
        }
    } catch (Exception e) {
        log.error(e.getMessage());
        return null;
    }
}

检查流程代码

List<Seat> seats = manifestService.getGroupSeatMap(
        reservation.getServiceBusId(),
        reservation.getDepartureDate(),
        depart.getGroupDestination(),
        destination.getGroupDestination(),
        request.getDepartBusId());
for (DetailCheckInResultResponse passenger : request.getPassenggerDepart()) {
    for (Seat seat : seats) {
        if (passenger.getSeat().equals(seat.getSeatNumber()) && "notAvailable".equals(seat.getSeatStatus())) {
            response.setStatus("failed");
            response.setMessage("Seat " + seat.getSeatNumber() + " not available");
            return response;
        }
    }
}
if (reservation.getReservationType()) {
    List<Seat> seatRets = manifestService.getGroupSeatMap(
            reservation.getServiceBusId(),
            reservation.getReturnDate(),
            destination.getGroupDestination(),
            depart.getGroupDestination(),
            request.getReturnBusId());
    for (DetailCheckInResultResponse passenger : request.getPassenggerReturn()) {
        for (Seat seat : seatRets) {
            if (passenger.getSeat().equals(seat.getSeatNumber()) && "notAvailable".equals(seat.getSeatStatus())) {
                response.setStatus("failed");
                response.setMessage("Seat " + seat.getSeatNumber() + " not available");
                return response;
            }
        }
    }
}

环境信息

  • Spring Framework: 2.3.1
  • Java: 1.8.0_211
  • Maven plugin: 2.22.0
  • DBMS: PostgreSQL 13.8

问题原因

核心问题出在getGroupSeatMap方法里的Seat newSeat = seat;这行代码:

  • 从seatBusRepository查询出来的Seat对象处于JPA的托管状态(由Hibernate会话管理)
  • newSeat = seat只是复制了对象引用,并没有创建新的对象实例,修改newSeat的属性本质上是修改了托管的实体对象
  • 当当前事务提交时,Hibernate会自动将托管实体的属性变化同步到数据库,导致主座位表被意外更新

解决方案

需要创建Seat实体的非托管副本,修改副本的属性不会触发数据库更新,以下是几种低资源消耗的实现方式:

方式1:手动构造副本(性能最优)

给Seat类添加复制构造函数,手动复制需要的属性:

public class Seat {
    // 原有属性和方法
    public Seat(Seat source) {
        this.id = source.id;
        this.seatNumber = source.seatNumber;
        this.seatStatus = source.seatStatus;
        // 复制其他业务需要的属性
    }
}

然后在getGroupSeatMap方法中替换Seat newSeat = seat;为:

Seat newSeat = new Seat(seat);

方式2:使用Spring BeanUtils复制属性

利用Spring提供的BeanUtils工具类自动复制属性,无需手动编写构造函数:

import org.springframework.beans.BeanUtils;

// 在getGroupSeatMap方法中
Seat newSeat = new Seat();
BeanUtils.copyProperties(seat, newSeat);

方式3:将查询实体转为非托管状态

注入EntityManager,在查询后将实体从会话中分离,使其变为非托管状态:

// 在getGroupSeatMap方法中
List<Seat> seatData = seatBusRepository.findByBusIdOrderByIdAsc(busId);
for (Seat seat : seatData) {
    entityManager.detach(seat);
}

此方式下直接修改seat对象也不会触发数据库更新,但需注意如果后续业务需要该实体的托管状态,可能会有影响。

验证修改

修改后,getGroupSeatMap方法中处理的是Seat的副本,修改其seatStatus只会影响内存中的对象,不会同步到主座位表,同时避免了JSON序列化的额外资源消耗。

内容的提问来源于stack exchange,提问作者David Sinaga

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:40:57