【问题标题】:Spring Data Rest Put not updating with immutable entity on SpringBoot 2.1.xSpring Data Rest Put 未使用 Spring Boot 2.1.x 中的不可变实体进行更新
【发布时间】:2019-10-06 18:20:51
【问题描述】:

更新到 spring boot starter 版本 2.1.5 后,put 请求不再使用不可变实体更新实体。

Put 请求适用于 Spring Boot Starter 2.0.3,但不适用于 2.1.4 和 2.1.5。但是,使用 Patch 方法可以正常工作。

由于我在 Kotlin 中工作,所以我的代码是用 Kotlin 编写的,但我也可以使用 Java 实体来重现它:

鉴于实体:

@Entity
data class MyEntityKt(
     @Id
     @GeneratedValue
     val id: UUID? = null,
     @Version
     val version: Long = 0,
     val name: String = "",
     val description: String = ""
)
@Value
@Wither
@Entity
@RequiredArgsConstructor
public class MyEntityJava {

    public MyEntityJava() {
        id = null;
        version = 0;
        name = "";
        description = "";
    }

    @Id
    @GeneratedValue
    private final UUID id;
    @Version
    private final long version;
    private final String name;
    private final String description;
}

以及对应的两个RestRepositories:

@RepositoryRestResource(path = "kotlin")
interface MyEntityRepository : JpaRepository<MyEntityKt, UUID>

@RepositoryRestResource(path = "java")
interface ImmutableEntityRepository : JpaRepository<MyEntityJava, UUID>

在我的测试 (@SpringBootTest) 中,我尝试了以下操作:

@Test
fun testPut() {
    val myEntity = myEntityRepository.save(MyEntityKt(name = "TestName", description = "TestDescription"))
    val update = this.mockMvc.perform(
        put("/kotlin/${myEntity.id}")
            .accept(MediaType.APPLICATION_JSON_UTF8)
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(
                """
                {
                    "name": "TestNameModified",
                    "description": "TestDescriptionModified"
                }
            """.trimIndent()
            )
    )
    .andReturn().response.contentAsByteArray.let { objectMapper.readValue(it, MyEntityKt::class.java) }

    val updatedEntity = myEntityRepository.findById(myEntity.id!!).get()

    assertThat(updatedEntity.name).isEqualTo("TestNameModified")    
    assertThat(updatedEntity.description).isEqualTo("TestDescriptionModified")
}

我得到的回应是:

{
    "name" : "TestName",
    "description" : "TestDescription",
    "_links" : {
    "self" : {
        "href" : "http://localhost/kotlin/fd83f256-3515-41b0-b9c7-7bfe53a367f8"
    },
    "myEntityKt" : {
        "href" : "http://localhost/kotlin/fd83f256-3515-41b0-b9c7-7bfe53a367f8"
    }
}

我做了一些调查,org.springframework.data.rest.webmvc.json.DomainObjectReader.MergingPropertyHandler#doWithPersistentProperty 我猜这将负责创建我的实体的更新版本以持续存在,似乎它只是在访问器上设置值而不是实际创建更新的对象。

在 Spring Boot 版本 2.0.3 上,测试用例通过,但在 2.1.5 上,实体没有更新,并且没有给出实体没有更新的实际原因(例如,日志输出)。我的猜测是,这与实体是不可变的这一事实有关,但我不明白为什么以前会这样。

编辑:我将测试用例上传到Github

【问题讨论】:

  • 因为您从一级缓存中获得了相同的实体,而不是新的。在对EntityManager 执行findById 调用clear 之前注入EntityManager,然后执行findById 以强制从数据库重新加载。此外,您的实体在 JPA 意义上也不是一成不变的,使用反射您仍然可以更改它们。你的 JPA 提供者可能有一些扩展,比如 hibernate 有 @Immutable 注释。
  • 感谢您的建议,但这并不能解决问题,我编辑了问题,您现在可以看到我返回的 json 响应也显示了实体的旧状态。

标签: java spring spring-boot kotlin spring-data-rest


【解决方案1】:

我找到了解决问题的方法:

  1. 将实体注释为不可变
  2. 使版本和 id 属性可变
@Entity
@org.springframework.data.annotation.Immutable
data class MyEntityKt(
     @Id
     @GeneratedValue
     var id: UUID? = null,
     @Version
     var version: Long = 0,
     val name: String = "",
     val description: String = ""
)

Immutable 注释导致我得到正确更新的 json 响应,但它使用我更新的值向数据库中插入了一个新条目。

因此,要使其覆盖现有实体,我必须使 id 和版本可变。

在我发送更新实体的 json 中,我包含了用于后续更新的版本,如下所示:

{
    "name": "TestNameModified",
    "description": "TestDescriptionModified",
    "version": 0
}

【讨论】:

  • 为什么 ID 是可变的?您不应该更改数据库 ID,因为这是不好的做法。如果是这样,那么您应该删除当前实体并创建新实体。数据库序列应该生成新的 ID...
  • @Martin Čuka 在旧实体和不可变的更新实体合并期间,Spring 似乎没有在更新实体上设置 ID,这导致了新的数据库条目。跨度>
猜你喜欢
  • 2020-05-29
  • 2020-02-28
  • 2018-05-04
  • 1970-01-01
  • 2016-01-08
  • 1970-01-01
  • 2018-01-16
  • 1970-01-01
  • 2019-09-30
相关资源
最近更新 更多