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
相关产品推荐
相关产品推荐

