Kotlin copy() 函数正在 JPA 实体上创建一个新 ID,从而产生一个新行

Posted

技术标签:

【中文标题】Kotlin copy() 函数正在 JPA 实体上创建一个新 ID,从而产生一个新行【英文标题】:Kotlin copy() function is creating a new Id on a JPA Entity resulting in a new row 【发布时间】:2019-03-25 09:29:48 【问题描述】:

我在 spring 控制器上有一个更新方法,它接收请求并使用 copy 将内容复制到加载的实体中。一旦调用了 copy(),实体上的 id 属性就会更改为新属性。

@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()

据我所知,副本不应该创建新对象,而是使用请求中的新值更改现有对象。为什么要为 id 分配一个新的 UUID?

谢谢

【问题讨论】:

copy() 顾名思义,创建对象的副本。如果它改变了原始对象,它的 vals 就会被改变,而 vals 不能被改变。 是的,所以 updatedSociety 是从存储库加载的社团对象的副本,并使用副本中的值进行更新......为什么在 updatedSociety 上会有一个新的 UUID? 因为唯一复制的属性是在数据类的主构造函数中声明的属性。 id 是一个 val,由超类用 new UUID() 初始化。再一次,一个 val 不可能被重新分配。所以它不可能有除new UUID() 之外的任何其他值。此外,文档说:请注意,编译器仅将主构造函数中定义的属性用于自动生成的函数。。其他说明:为什么要自己分配一个UUID,还要指定这个uuid应该是JPA生成的? 【参考方案1】:

根据this link,只有括号之间定义的属性将在 copy() 函数中使用,然后 id 和其他从超类继承的属性将不会被使用。我测试过了。

类主体中声明的属性

注意编译器只使用 在主构造函数中定义的属性 自动生成的函数。从 生成的实现,在类体内声明它:

数据类 Person(val name: String) var age: Int = 0

只有属性名称会在 toString()、equals()、hashCode() 和 copy() 实现中使用,并且 只有一个组件函数component1()。虽然两个 Person 对象可以有不同的年龄,他们将被平等对待。

但是,我的解决方案是:

不要使用copy() 函数。只需更改society 的属性并保存即可。您可以更改val 的属性,但不能更改引用(禁止society = something)。

当你使用copy() 时,它会在堆中生成一个新对象,并且它的引用是延迟的(hashCode() 是延迟的)。

所以我认为将 Society 类属性改为 var 并使用以下代码一定很好:

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

另外,我认为在数据类中使用继承会造成混淆,因此最好避免使用它。

【讨论】:

感谢 Mostafa,如果我将属性更改为 var,上面的代码就可以工作。

以上是关于Kotlin copy() 函数正在 JPA 实体上创建一个新 ID,从而产生一个新行的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Kotlin 在 Spring JPA 中正确查询实体

Kotlin JPA 实体 ID

Spring JPA/Hibernate Repository findAll 在 Kotlin 中默认执行 N+1 个请求而不是 JOIN

在 Kotlin 中使用 Jpa 注释从基类继承父属性

JPA在Kotlin和Glassfish中以双向关系无限递归

整合springboot+kotlin+gradle+jpa的demo学习笔记