所以我正在处理一些Spring Data JPA代码,实现是Hibernate。我创建了两个具有OneToMany关系的实体。这是实体:
@Entity
@Table(name = "clients")
data class Client (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
@Column(unique = true)
val name: String,
@Column(unique = true)
val clientKey: String,
val clientSecret: String,
val enabled: Boolean,
val accessTokenTimeoutSecs: Int,
val refreshTokenTimeoutSecs: Int,
val authCodeTimeoutSecs: Int,
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "clientId")
val clientRedirectUris: List<ClientRedirectUri>
)
@Entity
@Table(
name = "client_redirect_uris",
uniqueConstraints = [
UniqueConstraint(columnNames = [
"clientId",
"redirectUri"
])
]
)
data class ClientRedirectUri (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
val clientId: Long,
val redirectUri: String
)
所以,我的想法是每次我保存Client时,应该更新ClientRedirectUri引用。但是,这没有正确发生。这是我的持久性代码:
authClient = TestData.createClient(accessTokenTimeoutSecs, refreshTokenTimeoutSecs).copy(
name = validClientName,
clientKey = validClientKey,
clientSecret = "{bcrypt}$encodedSecret"
)
authClient = clientRepo.save(authClient)
val clientRedirectUri = ClientRedirectUri(0, authClient.id, "http://somewhere.com/authcode/code")
authClient = authClient.copy(clientRedirectUris = listOf(clientRedirectUri))
authClient = clientRepo.save(authClient)
clientRepo.save(authClient.copy(clientRedirectUris = listOf(clientRedirectUri.copy(redirectUri = "uri_1"))))
我首先创建authClient变量,然后将其持久保存到数据库中。现在已经为其分配了ID,我将该ID用于ClientRedirectUri实体的外键关系。设置好子实体并将其添加到authClient后,我再次将其保存()。到目前为止,一切都很完美。
然后,作为测试,我使用Kotlin copy()方法创建了原始对象的克隆,但是随后我将clientRedirectUris属性替换为带有新URI的新列表。这里的目标是一成不变地修改实体,然后在数据库中对其进行更新。
然后导致异常:
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["FKCW186RT6R4DRLEKPENRLQDNL5: PUBLIC.CLIENT_REDIRECT_URIS FOREIGN KEY(CLIENT_ID) REFERENCES PUBLIC.CLIENTS(ID) (1)"; SQL statement:
delete from clients where id=? [23503-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
实际上,它不是在进行更新操作,而是先删除Client引用,然后再插入一个新的引用。这不是我想要的,我希望它进行更新。
现在,经过大量的实验,我发现有些困惑。让我们对代码示例的最后一行进行更改,然后返回保存的实体:
authClient = clientRepo.save(authClient.copy(clientRedirectUris = listOf(clientRedirectUri.copy(redirectUri = "uri_1"))))
仅需进行一些细微的更改即可更改行为,并且JPA / Hibernate最终完美地处理了事情。原始的Client实体(根据记录的SQL)不会被触摸,而是只处理ClientRedirectUri实体的插入/删除操作。
为什么会发生这种情况,将来如何预防?