Java 17 + MySQL 8.0 机票系统数据库设计:5张核心表与3个关键业务逻辑实现
·
Java 17 + MySQL 8.0 机票系统数据库设计:5张核心表与3个关键业务逻辑实现
在当今数字化出行时代,一个高效可靠的机票预订系统对航空公司和旅客都至关重要。本文将深入探讨基于Java 17和MySQL 8.0的机票系统数据库设计与核心业务实现,提供可直接应用于生产环境的解决方案。
1. 数据库架构设计与核心表实现
1.1 数据库选型与版本考量
MySQL 8.0作为当前主流的关系型数据库,相比早期版本提供了多项关键改进:
- 事务性能提升 :优化了InnoDB引擎,事务处理能力提高2倍
- JSON支持增强 :原生JSON数据类型和丰富的JSON函数
- 窗口函数 :支持OVER子句等高级分析功能
- 原子DDL :确保数据字典操作的原子性
-- 创建数据库时指定字符集和排序规则
CREATE DATABASE airline_reservation
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
1.2 五张核心表设计
1.2.1 用户表(customer)
CREATE TABLE customer (
customer_id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL COMMENT '使用BCrypt加密',
real_name VARCHAR(100) NOT NULL,
gender ENUM('MALE', 'FEMALE', 'OTHER'),
birth_date DATE,
phone VARCHAR(20) NOT NULL,
email VARCHAR(100),
id_card VARCHAR(18) NOT NULL UNIQUE COMMENT '身份证号',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_phone (phone),
INDEX idx_id_card (id_card)
) ENGINE=InnoDB COMMENT='旅客信息表';
1.2.2 航班表(flight)
CREATE TABLE flight (
flight_id BIGINT PRIMARY KEY AUTO_INCREMENT,
flight_number VARCHAR(10) NOT NULL COMMENT '如CA1234',
airline_code VARCHAR(2) NOT NULL COMMENT '航空公司代码',
aircraft_type VARCHAR(20) NOT NULL,
departure_airport VARCHAR(50) NOT NULL,
arrival_airport VARCHAR(50) NOT NULL,
departure_time DATETIME NOT NULL,
arrival_time DATETIME NOT NULL,
total_seats INT NOT NULL COMMENT '总座位数',
available_seats INT NOT NULL COMMENT '可用座位数',
base_price DECIMAL(10,2) NOT NULL,
status ENUM('SCHEDULED', 'DELAYED', 'CANCELLED', 'COMPLETED') DEFAULT 'SCHEDULED',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_departure (departure_airport, departure_time),
INDEX idx_arrival (arrival_airport, arrival_time),
INDEX idx_flight_number (flight_number),
CONSTRAINT chk_time CHECK (arrival_time > departure_time)
) ENGINE=InnoDB COMMENT='航班信息表';
1.2.3 订单表(booking_order)
CREATE TABLE booking_order (
order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_number VARCHAR(20) NOT NULL UNIQUE COMMENT '订单编号',
customer_id BIGINT NOT NULL,
flight_id BIGINT NOT NULL,
seat_class ENUM('ECONOMY', 'BUSINESS', 'FIRST') NOT NULL,
seat_number VARCHAR(10),
passenger_name VARCHAR(100) NOT NULL,
passenger_id_card VARCHAR(18) NOT NULL,
contact_phone VARCHAR(20) NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
payment_status ENUM('UNPAID', 'PAID', 'REFUNDED', 'PARTIAL_REFUND') DEFAULT 'UNPAID',
order_status ENUM('PENDING', 'CONFIRMED', 'CANCELLED', 'COMPLETED') DEFAULT 'PENDING',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customer(customer_id),
FOREIGN KEY (flight_id) REFERENCES flight(flight_id),
INDEX idx_customer (customer_id),
INDEX idx_flight (flight_id),
INDEX idx_order_number (order_number)
) ENGINE=InnoDB COMMENT='订单表';
1.2.4 管理员表(admin)
CREATE TABLE admin (
admin_id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
real_name VARCHAR(100) NOT NULL,
role ENUM('SUPER_ADMIN', 'FLIGHT_MANAGER', 'ORDER_MANAGER') NOT NULL,
phone VARCHAR(20) NOT NULL,
email VARCHAR(100) NOT NULL,
last_login TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB COMMENT='管理员表';
1.2.5 机型表(aircraft)
CREATE TABLE aircraft (
aircraft_id BIGINT PRIMARY KEY AUTO_INCREMENT,
model VARCHAR(50) NOT NULL UNIQUE COMMENT '机型名称',
manufacturer VARCHAR(100) NOT NULL,
economy_seats INT NOT NULL,
business_seats INT NOT NULL,
first_seats INT NOT NULL,
total_seats INT GENERATED ALWAYS AS (economy_seats + business_seats + first_seats) STORED,
cruising_speed INT COMMENT '巡航速度(km/h)',
max_range INT COMMENT '最大航程(km)',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB COMMENT='机型信息表';
1.3 索引优化策略
为提高查询性能,我们在关键字段上建立了索引:
- 用户表 :phone、id_card
- 航班表 :departure_airport+departure_time、arrival_airport+arrival_time
- 订单表 :customer_id、flight_id、order_number
提示:对于高频查询但更新较少的表,可以考虑使用覆盖索引来避免回表操作。
2. 核心业务逻辑实现
2.1 订单创建流程
订单创建是系统的核心功能,需要处理并发预订和库存扣减问题。我们采用乐观锁机制确保数据一致性。
@Service
@Transactional
public class BookingServiceImpl implements BookingService {
private final FlightRepository flightRepository;
private final OrderRepository orderRepository;
private final IdGenerator idGenerator;
@Override
public OrderDTO createOrder(OrderRequest request) {
// 1. 验证航班信息
Flight flight = flightRepository.findById(request.getFlightId())
.orElseThrow(() -> new BusinessException("航班不存在"));
// 2. 检查座位可用性
if (flight.getAvailableSeats() <= 0) {
throw new BusinessException("该航班已无余票");
}
// 3. 创建订单(使用乐观锁)
int updated = flightRepository.reduceSeatWithLock(
flight.getFlightId(),
flight.getVersion()
);
if (updated == 0) {
throw new ConcurrentBookingException("座位已被其他用户预订,请重新选择");
}
// 4. 生成订单
BookingOrder order = new BookingOrder();
order.setOrderNumber(idGenerator.generateOrderNumber());
order.setCustomerId(request.getCustomerId());
order.setFlightId(flight.getFlightId());
order.setSeatClass(request.getSeatClass());
order.setPassengerName(request.getPassengerName());
order.setPassengerIdCard(request.getPassengerIdCard());
order.setContactPhone(request.getContactPhone());
order.setTotalAmount(calculatePrice(flight, request.getSeatClass()));
order.setPaymentStatus(PaymentStatus.UNPAID);
order.setOrderStatus(OrderStatus.PENDING);
orderRepository.save(order);
// 5. 返回订单DTO
return convertToDTO(order);
}
private BigDecimal calculatePrice(Flight flight, SeatClass seatClass) {
// 根据舱位等级计算价格逻辑
// ...
}
}
2.2 航班查询优化
航班查询需要考虑多种筛选条件和分页需求,我们使用JPA Specification实现动态查询。
@Repository
public interface FlightRepository extends JpaRepository<Flight, Long>,
JpaSpecificationExecutor<Flight> {
@Modifying
@Query("UPDATE Flight f SET f.availableSeats = f.availableSeats - 1, " +
"f.version = f.version + 1 WHERE f.flightId = :flightId AND " +
"f.version = :version")
int reduceSeatWithLock(@Param("flightId") Long flightId,
@Param("version") Long version);
}
@Service
public class FlightQueryServiceImpl implements FlightQueryService {
private final FlightRepository flightRepository;
@Override
public Page<FlightDTO> searchFlights(FlightQuery query, Pageable pageable) {
Specification<Flight> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(query.getDepartureAirport())) {
predicates.add(cb.equal(
root.get("departureAirport"),
query.getDepartureAirport()
));
}
if (StringUtils.isNotBlank(query.getArrivalAirport())) {
predicates.add(cb.equal(
root.get("arrivalAirport"),
query.getArrivalAirport()
));
}
if (query.getDepartureDate() != null) {
predicates.add(cb.between(
root.get("departureTime"),
query.getDepartureDate().atStartOfDay(),
query.getDepartureDate().plusDays(1).atStartOfDay()
));
}
if (query.getMinPrice() != null) {
predicates.add(cb.greaterThanOrEqualTo(
root.get("basePrice"),
query.getMinPrice()
));
}
if (query.getMaxPrice() != null) {
predicates.add(cb.lessThanOrEqualTo(
root.get("basePrice"),
query.getMaxPrice()
));
}
predicates.add(cb.greaterThan(
root.get("availableSeats"),
0
));
return cb.and(predicates.toArray(new Predicate[0]));
};
return flightRepository.findAll(spec, pageable)
.map(this::convertToDTO);
}
}
2.3 库存扣减与并发控制
机票系统面临的主要挑战之一是高并发下的库存准确性问题。我们采用多种策略确保数据一致性:
- 数据库层面 :使用乐观锁(version字段)
- 应用层面 :分布式锁(Redis实现)
- 业务层面 :预留座位机制
@Service
public class InventoryServiceImpl implements InventoryService {
private final FlightRepository flightRepository;
private final RedisLockHelper redisLockHelper;
private final BookingReservationRepository reservationRepository;
private static final String FLIGHT_LOCK_PREFIX = "flight:lock:";
private static final long LOCK_EXPIRE_TIME = 30; // 秒
@Override
public boolean reserveSeat(Long flightId, Long customerId, SeatClass seatClass) {
String lockKey = FLIGHT_LOCK_PREFIX + flightId;
try {
// 获取分布式锁
boolean locked = redisLockHelper.tryLock(lockKey, LOCK_EXPIRE_TIME);
if (!locked) {
throw new ConcurrentBookingException("系统繁忙,请稍后再试");
}
Flight flight = flightRepository.findById(flightId)
.orElseThrow(() -> new BusinessException("航班不存在"));
if (flight.getAvailableSeats() <= 0) {
return false;
}
// 创建预留记录
BookingReservation reservation = new BookingReservation();
reservation.setFlightId(flightId);
reservation.setCustomerId(customerId);
reservation.setSeatClass(seatClass);
reservation.setExpireTime(LocalDateTime.now().plusMinutes(15));
reservationRepository.save(reservation);
// 扣减库存
flight.setAvailableSeats(flight.getAvailableSeats() - 1);
flightRepository.save(flight);
return true;
} finally {
// 释放锁
redisLockHelper.unlock(lockKey);
}
}
@Scheduled(fixedRate = 60000) // 每分钟检查一次过期预留
public void cleanupExpiredReservations() {
List<BookingReservation> expired = reservationRepository
.findByExpireTimeBefore(LocalDateTime.now());
if (!expired.isEmpty()) {
List<Long> flightIds = expired.stream()
.map(BookingReservation::getFlightId)
.distinct()
.collect(Collectors.toList());
// 批量恢复库存
flightRepository.batchIncreaseSeats(flightIds, expired.size());
// 删除过期预留
reservationRepository.deleteAll(expired);
}
}
}
3. 高级特性与性能优化
3.1 分库分表策略
当系统规模扩大时,单一数据库可能成为瓶颈。我们设计了以下分片策略:
- 水平分片 :按航班日期分片,将不同日期的航班数据分散到不同库
- 垂直分片 :将用户信息、订单信息和航班信息分离到不同库
public class FlightShardingAlgorithm implements PreciseShardingAlgorithm<LocalDate> {
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<LocalDate> shardingValue) {
// 按年份分库,每年一个库
int year = shardingValue.getValue().getYear();
String dbSuffix = "db_" + (year % 2); // 示例中只分2个库
for (String each : availableTargetNames) {
if (each.endsWith(dbSuffix)) {
return each;
}
}
throw new IllegalArgumentException();
}
}
3.2 缓存策略设计
合理使用缓存可以显著提高系统响应速度:
- 航班信息缓存 :使用Redis缓存热门航线航班信息
- 查询结果缓存 :缓存常见查询条件组合的结果
- 本地缓存 :使用Caffeine缓存少量高频访问数据
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000);
}
@Bean
public CacheManager localCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineConfig());
return cacheManager;
}
}
@Service
@CacheConfig(cacheNames = "flights")
public class FlightCacheServiceImpl implements FlightCacheService {
private final FlightRepository flightRepository;
@Cacheable(key = "#flightId", unless = "#result == null")
@Override
public FlightDTO getFlightById(Long flightId) {
return flightRepository.findById(flightId)
.map(this::convertToDTO)
.orElse(null);
}
@Cacheable(key = "T(com.example.util.CacheKeyGenerator).generateKey(#query)")
@Override
public List<FlightDTO> searchFlights(FlightQuery query) {
// 实际查询逻辑
}
}
3.3 数据库连接池优化
正确的连接池配置对系统稳定性至关重要:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| maximumPoolSize | CPU核心数 * 2 + 有效磁盘数 | 最大连接数 |
| minimumIdle | 同maximumPoolSize | 最小空闲连接 |
| idleTimeout | 600000 (10分钟) | 空闲连接超时时间 |
| maxLifetime | 1800000 (30分钟) | 连接最大生命周期 |
| connectionTimeout | 30000 (30秒) | 获取连接超时时间 |
# application.yml配置示例
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 20
idle-timeout: 600000
max-lifetime: 1800000
connection-timeout: 30000
pool-name: BookingHikariCP
4. 安全设计与异常处理
4.1 数据安全措施
- 敏感数据加密 :
- 密码使用BCrypt加密
- 身份证号等敏感信息加密存储
public class SecurityUtils {
private static final int BCRYPT_STRENGTH = 12;
public static String encryptPassword(String rawPassword) {
return BCrypt.hashpw(rawPassword, BCrypt.gensalt(BCRYPT_STRENGTH));
}
public static boolean checkPassword(String rawPassword, String encryptedPassword) {
return BCrypt.checkpw(rawPassword, encryptedPassword);
}
public static String encryptIdCard(String idCard) {
// 使用AES加密身份证号
// ...
}
public static String decryptIdCard(String encryptedIdCard) {
// AES解密
// ...
}
}
4.2 异常处理框架
设计统一的异常处理机制,提供友好的错误信息:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
ErrorResponse response = new ErrorResponse(
"BUSINESS_ERROR",
ex.getMessage(),
System.currentTimeMillis()
);
return ResponseEntity.badRequest().body(response);
}
@ExceptionHandler(ConcurrentBookingException.class)
public ResponseEntity<ErrorResponse> handleConcurrentBookingException(ConcurrentBookingException ex) {
ErrorResponse response = new ErrorResponse(
"CONCURRENT_BOOKING",
ex.getMessage(),
System.currentTimeMillis()
);
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
ErrorResponse response = new ErrorResponse(
"INTERNAL_ERROR",
"系统内部错误,请稍后再试",
System.currentTimeMillis()
);
return ResponseEntity.internalServerError().body(response);
}
}
public class ErrorResponse {
private String code;
private String message;
private long timestamp;
// 构造方法、getter和setter
}
4.3 接口安全设计
- 认证与授权 :使用JWT进行接口认证
- 参数校验 :使用Hibernate Validator验证输入
- 防重放攻击 :使用nonce和timestamp机制
- 限流保护 :使用Guava RateLimiter或Redis实现接口限流
@RestController
@RequestMapping("/api/bookings")
public class BookingController {
private final BookingService bookingService;
@PostMapping
@RateLimit(value = 10, duration = 1, unit = TimeUnit.MINUTES)
public ResponseEntity<OrderDTO> createOrder(
@Valid @RequestBody OrderRequest request,
@RequestHeader("Authorization") String token) {
// 验证JWT token
if (!JwtUtils.validateToken(token)) {
throw new UnauthorizedException("无效的访问令牌");
}
Long customerId = JwtUtils.getCustomerId(token);
request.setCustomerId(customerId);
OrderDTO order = bookingService.createOrder(request);
return ResponseEntity.ok(order);
}
}
public @interface RateLimit {
int value() default 10;
int duration() default 1;
TimeUnit unit() default TimeUnit.MINUTES;
}
@Aspect
@Component
public class RateLimitAspect {
private final Cache<String, AtomicInteger> counterCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
String key = generateKey(joinPoint);
AtomicInteger count = counterCache.get(key, k -> new AtomicInteger(0));
if (count.incrementAndGet() > rateLimit.value()) {
throw new RateLimitExceededException("操作过于频繁,请稍后再试");
}
try {
return joinPoint.proceed();
} finally {
// 计数处理
}
}
private String generateKey(ProceedingJoinPoint joinPoint) {
// 生成基于方法和参数的key
}
}
更多推荐



所有评论(0)