在线旅游预订系统的核心是把旅游资源(线路、库存)与用户订单通过关系型数据库可靠地关联起来,并用Java服务端处理业务逻辑。下面以MySQL为存储、Java为开发语言,逐步实现一个支持线路浏览、下单与订单查询的最小可用系统。

一、数据库设计
在MySQL中,我们需要三张核心表:旅客表、旅游线路表、订单表。旅客表记录用户基本信息;线路表保存旅游产品名称、价格、库存;订单表关联旅客与线路,并标记订单状态。合理的索引能显著提升查询效率。
下面给出建表语句,其中线路表的stock字段表示剩余库存,订单表对user_id与route_id建立联合唯一索引,防止同一用户重复创建未支付订单造成数据混乱。
CREATE TABLE traveler ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE travel_route ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE booking_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, route_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL, UNIQUE KEY uk_user_route (user_id, route_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、Java项目结构
采用Spring Boot加MyBatis的组合可以降低配置成本。控制器层接收HTTP请求,服务层编写预订逻辑,持久层通过Mapper操作MySQL。这种分层让代码职责清晰,也方便后续单元测试。
在pom.xml中引入必要依赖后,我们通过注解方式编写Mapper。下面的接口定义了扣减库存与插入订单两个方法,二者将在同一个事务中调用,保证一致性。
@Mapper
public interface BookingMapper {
@Update("UPDATE travel_route SET stock = stock - 1 WHERE id = #{routeId} AND stock > 0")
int decreaseStock(@Param("routeId") Long routeId);
@Insert("INSERT INTO booking_order(user_id, route_id, status, create_time) " +
"VALUES(#{userId}, #{routeId}, 0, NOW())")
int insertOrder(@Param("userId") Long userId, @Param("routeId") Long routeId);
}
三、预订业务逻辑实现
下单时要先扣库存再写订单。由于SQL中stock > 0的条件限制,若库存不足则更新行数为0,此时应回滚并提示售罄。利用Spring的@Transactional注解,可确保扣减与插入要么都成功要么都失败。
以下服务方法演示了完整流程:先调用decreaseStock,返回影响行数为0时直接抛异常;否则插入订单。这样即使高并发请求同时进来,数据库行锁也会串行化库存修改,避免超卖。
@Service
public class BookingService {
@Autowired
private BookingMapper bookingMapper;
@Transactional(rollbackFor = Exception.class)
public void book(Long userId, Long routeId) {
int updated = bookingMapper.decreaseStock(routeId);
if (updated == 0) {
throw new RuntimeException("库存不足或线路不存在");
}
bookingMapper.insertOrder(userId, routeId);
}
}
四、接口暴露与连接池配置
最后用REST控制器把预订能力开放给前端。同时,在配置文件中设置HikariCP连接池,避免每次请求都新建物理连接。合理的池大小通常接近数据库最大连接数的三分之二。
下面代码展示了一个简化的下单接口,实际项目中还应加入参数校验、登录态校验与全局异常处理。通过Postman或简单页面即可完成预订验证。
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private BookingService bookingService;
@PostMapping("/book")
public String book(@RequestParam Long userId, @RequestParam Long routeId) {
bookingService.book(userId, routeId);
return "预订成功";
}
}
五、总结与扩展
上述方案用MySQL事务与唯一索引解决了旅游预订中的超卖与重复下单问题,Java侧通过分层架构保持代码可维护。对于演示或小规模应用已经足够。
若日后用户量增长,可引入Redis缓存线路信息、用消息队列异步处理订单状态流转,并将库存扣减改为预扣模式。但无论怎么演进,本文的库表关系与事务边界都是系统稳定运行的基石。