Kotlin JPA Encapsulate OneToMany

I am using JPA with Kotlin and oppose a problem trying to encapsulate OneToMany relationships. I can easily achieve this in Java, but I have some problems related to the fact that Kotlin only has properties and fields in classes.

I have an order, and the order has one for many items. The order object has a MutableList from LineItem, but the get method SHOULD NOT return a modified list or anything that the caller could potentially change, as this interrupts the encapsulation. The order class should be responsible for managing a set of items and ensuring that all business rules / validations are followed.

This is the code that I have provided so far. I mainly use the backing property, which is a MutableList that will process the Order class, and then a transient property appears that returns Iterable, and Collections.unmodifiableList(_lineItems)ensures that even if the caller receives the list and drops it in the MutableList, t change it.

Is there a better way to ensure encapsulation and integrity. Maybe I'm just too protective of my design and approach. Ideally, no one should use getter to get and change a list, but this happens.

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
    }
}
+4
source share
2 answers

Your basic idea is correct, but I would suggest a few small changes:

@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
}

Notes:

  • id . 0 " ".
  • , (. no-arg)
  • id
  • id equals ( id ), , a data class
  • lineItems , @Transient
  • addLineItem, Unit, += (plusAssign).
+1

, , equals, hashCode toString .

, . .

private val _lineItems, backing ( IntelliJ val lineItems.

( 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
)
+2

Source: https://habr.com/ru/post/1683551/


All Articles