我正在使用JPA与Kotlin并试图封装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
}
}
答案 0 :(得分:4)
首先,我喜欢以这种方式使用数据类,您可以免费获得equals
,hashCode
和toString
。
我将所有非集合的属性放入主构造函数中。我将这些集合放入了类体中。
在那里你可以创建一个private val _lineItems
,这是一个支持属性(在创建val lineItems
属性后可以由IntelliJ生成。
您的私人支持字段有一个可变集合(我希望尽可能使用Set
),可以使用addNewLineItem
方法进行更改。当你获得属性lineItems
时,你会得到一个不可变的集合。 (这是通过在可变列表上使用.toList()
来完成的。
这样就可以封装集合,而且它仍然非常简洁。
import javax.persistence.*
@Entity
data class OrderEntity(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
val id: Long? = -1,
var firstName: String,
var lastName: String
) {
@OneToMany(cascade = [(CascadeType.ALL)], fetch = FetchType.LAZY, mappedBy = "order")
private val _lineItems = mutableListOf<LineItem>()
@Transient
val lineItems = _lineItems.toList()
fun addLineItem(newItem: LineItem) = this._lineItems.plusAssign(newItem)
}
@Entity
data class LineItem(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
val id: Long? = -1,
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "order_id")
val order: OrderEntity? = null
)
答案 1 :(得分:2)
你的基本想法是正确的,但我会提出一些细微的修改:
@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作为默认值已经意味着&#34;没有保留&#34;。id
将自动生成,不应位于构造函数id
不是主构造函数equals
的一部分会产生错误的结果(因为id不会是比较的一部分),除非以正确的方式覆盖,所以我不会使用data class
lineItems
是没有支持字段的属性,则不需要@Transient
注释addLineItem
使用块体,因为它返回Unit
,这也使您能够使用+=
运算符而不是显式函数调用({{1 }})。