Kotlin copy() function is creating a new Id on a JPA Entity resulting in a new row

Viewed 1488

I have an update method on a spring controller which takes in a request and uses copy to copy the contents into a loaded entity. As soon as copy() is called, the id property on the entity is changed to a new one.

@PutMapping("/{id}")
    fun update(@PathVariable id: UUID, @RequestBody request: UpdateSocietyRequest): ResponseEntity<SocietyUpdatedResponse> {
        val society = societyRepository.findById(id).orElse(null) ?: return notFound().build()
        val updatedSociety = society.copy(
                name = request.name,
                phone = request.phone,
                address = Address(
                        request.addressLine1,
                        request.addressLine2,
                        request.addressLine3,
                        request.city,
                        request.state,
                        request.zipCode
                )
        )
        societyRepository.save(updatedSociety)
        return ok(SocietyUpdatedResponse(updatedSociety.id, updatedSociety.name, updatedSociety.phone))
    }

Entity.kt

@MappedSuperclass
@JsonIgnoreProperties(value = ["createdOn, updatedOn"], allowGetters = true)
@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: UUID = UUID.randomUUID()

    @Column(nullable = false, updatable = false)
    @CreatedDate
    var createdOn: LocalDateTime = LocalDateTime.now()

    @Column(nullable = true)
    @LastModifiedDate
    var updatedOn: LocalDateTime? = null

    @Column(nullable = false, updatable = false)
    @CreatedBy
    var createdBy: String? = null

    @Column(nullable = true)
    @LastModifiedBy
    var updatedBy: String? = null
}

@Embeddable
data class Address(val addressLine1: String,
                   val addressLine2: String,
                   val addressLine3: String,
                   val city: String,
                   val state: String,
                   val zipCode: Int)

@Entity
data class Society(
        @NotNull
        val name: String,

        @NotNull
        val phone: String,

        @Embedded
        val address: Address
) : BaseEntity()

As far as I know, copy shouldn't created a new object but changes the existing with the new valued from the request. Why id being assigned a new UUID?

Thanks

1 Answers

According to this link only properties defined between parentheses will be used in the copy() function then id and other properties that inherited from the superclass will not be used. I tested it.

Properties Declared in the Class Body

Note that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. To exclude a property from the generated implementations, declare it inside the class body:

data class Person(val name: String) { var age: Int = 0 }

Only the property name will be used inside the toString(), equals(), hashCode(), and copy() implementations, and there will only be one component function component1(). While two Person objects can have different ages, they will be treated as equal.

However, my solution is:

Do not use the copy() function. Just change the properties of society and save it. You can change the properties of a val but you can not change the reference (society = something is forbidden).

When you use copy(), it generates a new object in heap and its reference is deferent(hashCode() is deferent).

So I think changing the Society class properties to var and using the following code must be good:

@Entity
data class Society(
        @NotNull
        var name: String,

        @NotNull
        var phone: String,

        @Embedded
        var address: Address
) : BaseEntity()

...

@PutMapping("/{id}")
    fun update(@PathVariable id: UUID, @RequestBody request: UpdateSocietyRequest): ResponseEntity<SocietyUpdatedResponse> {
        val society = societyRepository.findById(id).orElse(null) ?: return notFound().build()
        society.name = request.name
        society.phone = request.phone
        society.address = Address(
                        request.addressLine1,
                        request.addressLine2,
                        request.addressLine3,
                        request.city,
                        request.state,
                        request.zipCode
                )
        )
        societyRepository.save(society)
        return ok(SocietyUpdatedResponse(society.id, society.name, society.phone))
    }

Also, I think using inheritance in data classes can make confusion so it is better to avoid it.

Related