Kotlin JPA Encapsulate OneToMany

Viewed 13897

I am using JPA with Kotlin and coming against an issue trying to encapsulate a OneToMany relationship. This is something I can easily achieve in Java but having some issues due to Kotlin only having properties and no fields in classes.

I have an order, and an order has one to many line items. The order object has a MutableList of LineItem BUT the get method SHOULD NOT return a mutable list, or anything that the caller could potentially modify, as this breaks encapsulation. The order class should be responsible for managing collection of line items and ensuring all business rules / validations are met.

This is the code I've come up with thus far. Basically I'm using a backing property which is the MutableList which Order class will mutate, and then there is a transient property which returns Iterable, and Collections.unmodifiableList(_lineItems) ensure that even if caller gets the list, and cast it to MutableList they won't be able to modify it.

Is there a better way to enforce encapsulation and integrity. Perhaps I'm just being too defensive with my design and approach. Ideally no one should be using the getter to get and modify the list, but hey it 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
    }
}
2 Answers
Related