Kotlin JPA封装了OneToMany

gre*_*fox 5 jpa kotlin

我正在使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

Wil*_*zel 6

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

@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
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • id不需要可空。0 作为默认值已经意味着“不持久化”。
  • 使用主构造函数,不需要受保护的空主构造函数(请参阅no-arg 编译器插件
  • id会是自动生成的,不应该在构造函数
  • 因为id不是主构造函数的一部分equals会产生错误的结果(因为 id 不是比较的一部分),除非以正确的方式覆盖,所以我不会使用data class
  • 如果lineItems是没有支持字段的属性,则不需要@Transient注释
  • 最好使用块体 foraddLineItem因为它返回Unit,这也使您可以使用+=运算符而不是显式函数调用 ( plusAssign)。


Joh*_*eer 5

首先要使用的数据类这样你得到所有我的equalshashCode而且toString是免费的。

我将所有不是集合的属性放入主构造函数中。我将集合放入类主体。

在那里你可以创建一个private val _lineItems作为后备属性的属性(它可以在你创建val lineItems属性后由 IntelliJ 生成。

您的私有支持字段有一个可变集合(我更喜欢尽可能使用 a 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
)
Run Code Online (Sandbox Code Playgroud)

  • 这些类作为实体是有问题的。当它们被持久化时,它们会改变它们的哈希码。双向关联的“toString()”将永远递归。Kotlin 数据类并不是完美的实体。 (2认同)
  • 现在,当使用“@Entity”注释数据类时,IntelliJ 会发出警告 - *不建议将 equals()、hashCode() 和 toString() 的数据类实现用于 JPA 实体。它们可能会导致严重的性能和内存消耗问题。* (2认同)