如何用Kotlin优雅更新JPA对象?双向关联赋值难题求解
解决双向关联下不可变Order对象的关联初始化问题
针对你遇到的Order与OrderDetail双向关联、且Order的orderDetails为val无法重新赋值的问题,这里提供两种符合不可变设计原则的解决方案:
方案1:通过级联持久化+构造完整对象实现
核心思路是一次性构建包含完整关联关系的Order对象,利用JPA的级联持久化特性,只需要保存Order就能自动同步保存关联的OrderDetail,全程不需要修改Order的val属性。
步骤:
- 在Order实体的
orderDetails属性上添加级联注解,确保保存Order时自动保存关联的OrderDetail:
@Entity class Order( // 其他属性... @OneToMany(mappedBy = "order", cascade = [CascadeType.PERSIST, CascadeType.MERGE], orphanRemoval = true) val orderDetails: List<OrderDetail> = emptyList() )
- 修改Controller中的创建逻辑,先构建未持久化的Order对象,再关联OrderDetail,最后通过copy(或构造函数)创建包含完整关联的Order实例并级联保存:
@PostMapping fun create(@RequestBody createOrderRequest: CreateOrderRequest): ResponseEntity<ViewOrder> { // 1. 先构建未持久化的Order对象 val baseOrder = Order( status = OrderStatus.CONFIRMED, orderPlacedDate = Date(), totalPrice = BigDecimal.ZERO, totalWeight = BigDecimal.ZERO ) // 2. 构建关联到baseOrder的OrderDetail列表 val orderDetails = createOrderRequest.products.mapNotNull { productRepository.findById(it.productId).orElse(null)?.let { product -> OrderDetail( product = product, quantity = it.quantity, currencyCode = CurrencyCode.DZD, uom = product.uom, price = product.price * it.quantity, order = baseOrder ) } } // 3. 构建包含完整关联的Order实例(利用Kotlin data class的copy特性) val completeOrder = baseOrder.copy(orderDetails = orderDetails) // 4. 级联保存Order和所有OrderDetail val savedOrder = orderRepository.save(completeOrder) return ResponseEntity.status(HttpStatus.CREATED).body(savedOrder.toView()) }
这个方案完全遵循不可变设计,所有对象一旦创建就不再修改,同时减少了数据库操作次数。
方案2:使用val类型的可变集合
如果不想调整对象创建流程,可以将Order的orderDetails定义为val引用的可变集合,这样属性引用本身不可变(不能替换成其他集合),但可以往集合中添加元素,既满足不可变设计的核心要求,又能完成关联初始化。
步骤:
- 修改Order实体的集合类型:
@Entity class Order( // 其他属性... @OneToMany(mappedBy = "order", cascade = [CascadeType.PERSIST, CascadeType.MERGE], orphanRemoval = true) val orderDetails: MutableList<OrderDetail> = mutableListOf() )
- 调整Controller逻辑,直接往已保存的Order的集合中添加OrderDetail:
@PostMapping fun create(@RequestBody createOrderRequest: CreateOrderRequest): ResponseEntity<ViewOrder> { val order = orderRepository.save( Order( status = OrderStatus.CONFIRMED, orderPlacedDate = Date(), totalPrice = BigDecimal.ZERO, totalWeight = BigDecimal.ZERO ) ) val orderDetails = createOrderRequest.products.mapNotNull { productRepository.findById(it.productId).orElse(null)?.let { product -> OrderDetail( product = product, quantity = it.quantity, currencyCode = CurrencyCode.DZD, uom = product.uom, price = product.price * it.quantity, order = order ) } } // 往val引用的可变集合中添加元素,无需修改属性引用 order.orderDetails.addAll(orderDetails) val savedOrder = orderRepository.save(order) return ResponseEntity.status(HttpStatus.CREATED).body(savedOrder.toView()) }
注意事项:
- 双向关联中,
mappedBy = "order"指定OrderDetail是关联的维护端,因此只要设置OrderDetail的order属性,JPA就能正确维护数据库关联;同步Order的orderDetails是为了保证内存中对象模型的一致性。 - 方案1更贴合不可变设计的严格要求,方案2则是在不可变与代码改动量之间的折中。
内容的提问来源于stack exchange,提问作者user9132502
相关产品推荐
相关产品推荐

