我在Kotlin中使用JPA,遇到一个试图封装OneToMany关系的问题。这是我在Java中可以轻松实现的功能,但是由于Kotlin仅具有属性且类中没有字段,因此存在一些问题。

我有一个订单,一个订单有一对多订单项。订单对象具有LineItem的MutableList,但get方法不应返回可变列表或调用者可能修改的任何内容,因为这会破坏封装。订单类应负责管理订单项的收集并确保符合所有业务规则/验证。

这是我到目前为止提出的代码。基本上,我使用的是后备属性,即Order类将对其进行突变的MutableList,然后有一个返回Iterable的瞬时属性,并且Collections.unmodifiableList(_lineItems)确保即使调用者获得了该列表并将其转换为MutableList,它们也不会能够修改它。

有没有更好的方法来实现封装和完整性。也许我只是在我的设计和方法上过于防御。理想情况下,没有人应该使用getter来获取和修改列表,但是嘿,这确实发生了。

import java.util.*
import javax.persistence.*

@Entity
@Table(name = "order")
open class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: Long? = null

    @Column(name = "first_name")
    lateinit var firstName: String

    @Column(name = "last_name")
    lateinit var lastName: String

    @OneToMany(cascade = arrayOf(CascadeType.ALL), fetch = FetchType.LAZY, mappedBy = "order")
    private val _lineItems: MutableList<LineItem> = ArrayList()

    val lineItems: Iterable<LineItem>
    @Transient get() = Collections.unmodifiableList(_lineItems)

    protected constructor()

    constructor(firstName: String, lastName: String) {
        this.firstName = firstName
        this.lastName = lastName
    }

    fun addLineItem(newItem: LineItem) {
        // do some validation and ensure all business rules are met here

        this._lineItems.add(newItem)
    }
}

@Entity
@Table(name = "line_item")
open class LineItem {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: Long? = null

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "order_id", referencedColumnName = "id")
    lateinit var order: Order
        private set

    // whatever properties might be here

    protected constructor()

    constructor(order: Order) {
        this.order = order
    }
}

最佳答案

您的基本想法是正确的,但我会提出一些小的修改:

@Entity
class OrderEntity(
        var firstName: String,
        var lastName: String
) {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: Long = 0

    @OneToMany(cascade = [(CascadeType.ALL)], fetch = FetchType.LAZY, mappedBy = "order")
    private val _lineItems = mutableListOf<LineItem>()

    val lineItems get() = _lineItems.toList()

    fun addLineItem(newItem: LineItem) {
        _lineItems += newItem // ".this" can be omitted too
    }
}

@Entity
class LineItem(
        @ManyToOne(fetch = FetchType.LAZY, optional = false)
        @JoinColumn(name = "order_id")
        val order: OrderEntity? = null
){
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      val id: Long = 0
}

笔记:
  • id不必为可为空。 0作为默认值已经表示“未持久”。
  • 使用主构造函数,不需要 protected 空主构造函数(请参见no-arg compiler plug-in)
  • id将自动生成,并且不应在构造函数
  • ,因为id不属于主要构造函数equals会产生错误的结果(因为id不会成为比较的一部分),除非以正确的方式覆盖,所以我不会使用data class
  • 如果lineItems是没有后备字段的属性,则不需要@Transient批注
  • 最好使用一块块体作为addLineItem,因为它返回Unit,这也使您可以使用+=运算符而不是显式函数调用(plusAssign)。
  • 10-08 02:25