在我们的项目中,我们将kotlin与JPA一起使用。我们所有实体都是不可变的,因此无法直接设置我们实体的字段。您必须使用copy方法创建一个新实例。如果希望这些更改反映到数据库中,则必须使用显式函数调用来持久保存此新创建的实体。
一开始,这种方法对我们来说似乎很完美。但是,如今我们遇到了一些问题,例如某些实例在内存中发生了意外更改。
val instance1 = repository.findById(entityId)
repository.save(instance1.copy(deletedAt = Instant.now()))
..
..
assertNull(instance1.deletedAt())
在上面的代码中,从数据库中检索instance1,并使用copy方法设置了instance1的deleteAt字段,并将使用此copy方法创建的新实例传递到存储库的save方法。我们没有设置instance1的任何字段,而是创建一个新实例来进行这些更改。但是,断言行上的结果意外地为非空。
似乎,JPA持久性上下文(一级缓存)与kotlin的不可变和复制方法逻辑存在冲突。
使用JPA和不可变的Kotlin实体时,有人遇到这个问题,任何建议或最佳做法吗?
答案 0 :(得分:1)
I suspect the problem is that you're ignoring the return value from save()
. Its docs say:
Saves a given entity. Use the returned instance for further operations as the save operation might have changed the entity instance completely.
But you're not doing that; you're instead continuing to use the original instance which (as that says) may have changed.
Instead, store the return value from save()
, and use that thereafter. (Either by making instance1
a var
, or creating a new val
and not referring to instance1
afterward.)
(This isn't a Kotlin-specific problem, and is exactly the same in Java. JPA , Spring, &c work their magic by futzing with the bytecode, so can do things your code can't — such as changing immutable values. Most of the time you can ignore it, but this case makes it obvious.)
答案 1 :(得分:0)
不可移植类型与JPA的工作方式不兼容。
JPA围绕UnitOfWork的概念工作,这意味着从数据库中检索到的对象位于PersistedContext(一级缓存)中,一旦EntityManager关闭(在HTTP请求结束时在Web应用程序上),它们将被丢弃。
在刚从数据库中检索到的实体中使用copy
方法时,从当前会话中将复制的对象视为detached
,这意味着JPA和基础实现无法跟踪其更改(Hibernate / EclipseLink)很难确定需要触发哪条SQL语句(Insert / Update / Delete ????)
当您具有带有OneToMany关联和级联选项的复杂对象图时,事情就会变得更加复杂。
因此,不幸的是,我的建议是在使用JPA时避免使用不可变类型。