更新时未在返回的实体上设置标记为可更新 = 假的弹簧数据审核字段

Posted

技术标签:

【中文标题】更新时未在返回的实体上设置标记为可更新 = 假的弹簧数据审核字段【英文标题】:spring data auditing fields marked as updatable=false don't get set on the returned entity when updating 【发布时间】:2019-02-13 05:47:22 【问题描述】:

我正在使用 Spring Data 的注释在保存或更新我的实体时将审计数据添加到它们。当我创建实体时,createdBycreatedDatelastModifiedBylastModifiedDate 被设置在 repository.save() 返回的对象上。

ResourceEntity(id=ebbe1f3d-3359-4295-8c83-63eab21c4753, createdDate=2018-09-07T21:11:25.797, lastModifiedDate=2018-09-07T21:11:25.797, createdBy=5855070b-866f-4bc4-a18f-26b54f896a4b, lastModifiedBy=5855070b-866f-4bc4-a18f-26b54f896a4b)

不幸的是,当我调用repository.save() 来更新现有实体时,返回的对象没有设置createdBycreatedDate

 ResourceEntity(id=ebbe1f3d-3359-4295-8c83-63eab21c4753, createdDate=null, lastModifiedDate=2018-09-07T21:12:01.953, createdBy=null, lastModifiedBy=5855070b-866f-4bc4-a18f-26b54f896a4b)

所有字段都在数据库中正确设置,并且在我的服务类之外调用repository.findOne() 会返回一个所有字段设置正确的对象。

ResourceEntity(id=ebbe1f3d-3359-4295-8c83-63eab21c4753, createdDate=2018-09-07T21:11:25.797, lastModifiedDate=2018-09-07T21:12:01.953, createdBy=5855070b-866f-4bc4-a18f-26b54f896a4b, lastModifiedBy=5855070b-866f-4bc4-a18f-26b54f896a4b)

但是,如果我在调用repository.save() 更新实体后立即在服务中调用repository.findOne(),我还会返回一个对象,其中createdBycreatedDate 设置为null。

这是我的实体:

@Entity(name = "resource")
@EntityListeners(AuditingEntityListener.class)
@Table(name = "resource")
@Data
@EqualsAndHashCode(of = "id")
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ResourceEntity 

@Id
@org.hibernate.annotations.Type(type = "org.hibernate.type.PostgresUUIDType")
private UUID id;

@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdDate;

@LastModifiedDate
private LocalDateTime lastModifiedDate;

@CreatedBy
@Column(nullable = false, updatable = false)
@org.hibernate.annotations.Type(type = "org.hibernate.type.PostgresUUIDType")
private UUID createdBy;

@LastModifiedBy
@org.hibernate.annotations.Type(type = "org.hibernate.type.PostgresUUIDType")
private UUID lastModifiedBy;

这是我的服务:

@Component
public class ResourceService 
@Autowired
private ResourceRepository resourceRepository;

public ResourceEntity createResource(ResourceEntity resourceEntity) 
    return saveResource(resourceEntity);


public ResourceEntity updateResource(ResourceEntity resourceEntity) 
    return saveResource(resourceEntity);


public ResourceEntity getResource(UUID resourceId) 
    return resourceRepository.findOne(resourceId);


private ResourceEntity saveResource(ResourceEntity resourceEntity) 
    ResourceEntity savedResourceEntity = resourceRepository.save(resourceEntity);
    return savedResourceEntity;


这是我的测试:

def "Test update"() 
    given:
    UUID id = aRandom.uuid()
    Resource resource = aRandom.resource().id(id).build()
    Resource savedResource = resourceClient.createResource(resource)

    when:
    Resource updatedResource = aRandom.resource().id(id).build()
    updatedResource = resourceClient.updateResource(updatedResource)

    then:
    Resource result = resourceClient.getResource(id)
    assert result.id == updatedResource.id
    assert result.createdBy == updatedResource.createdBy
    assert result.creationDate == updatedResource.creationDate
    assert result.lastModifiedBy == updatedResource.lastModifiedBy
    assert result.lastModifiedDate == updatedResource.lastModifiedDate

【问题讨论】:

这是预期的行为。当设置了 updatable = false 时,你可以更新这个字段。 我认为这不是 spring 文档所说的预期行为:保存给定的实体。使用返回的实例进行进一步的操作,因为保存操作可能已经完全改变了实体实例。 如果我们应该使用返回的实例,那么它可以更好地准确地反映数据库中的内容。是的,update=false 阻止 save() 调用更新创建的字段,但它们应该使用数据库中的内容填充到返回的对象中。 对于一个简单的解决方案,您可以再次从数据库中获取实体。现在这个实体将拥有之前更新的字段。 【参考方案1】:

在最初的问题之后的几年,它仍然是一个问题。

虽然不是一个完美的解决方案,但我最终获取(通过findById())现有实体并在执行更新之前手动设置新实体上的@CreatedBy@CreatedDate 字段。 JPA 的审计框架没有自动处理这个问题或提供更好的方法来完成它,这既令人惊讶又令人沮丧。

【讨论】:

【参考方案2】:

当我需要添加审计信息时,我曾尝试过这样的操作。

我有一个这样的DataConfig 课程。

@Configuration
@EnableJpaAuditing
public class DataConfig 

    @Bean
    public AuditorAware<UUID> auditorProvider() 
        return new YourSecurityAuditAware();
    

    @Bean
    public DateTimeProvider dateTimeProvider() 
        return () -> Optional.of(Instant.from(ZonedDateTime.now()));
    

现在你需要AuditorAware 类来获取审计信息。 所以这个类会是这样的;

public class XPorterSecurityAuditAware implements AuditorAware<UUID> 

    @Override
    public Optional<UUID> getCurrentAuditor() 
        //you can change UUID format as well
        return Optional.of(UUID.randomUUID());
    

希望这会对你有所帮助。

【讨论】:

以上是关于更新时未在返回的实体上设置标记为可更新 = 假的弹簧数据审核字段的主要内容,如果未能解决你的问题,请参考以下文章

使用迁移到 Worklight 6.3 的现有 6.0.2 代码连接到本地时未在 Worklight 6.3 中获得更新

新标记已更新其位置,但旧标记未在离子原生谷歌地图中删除

Nhibernate 在子集合更新时未检测到父项的更改

UIbutton 未在 UITableViewCell 中更新其标记值

Ajax 发布返回成功但在调试时未在 VS 中达到断点

如何创建可更新的 CoreML 模型?