Search code examples
javaspring-boothibernatespring-data-jpadirty-checking

Why dirty checking doesn't work when updating entity in transaction


I am using Spring 3.3.0 with JPA.

Here is entity:

@Data
@Entity
@EqualsAndHashCode(of = {"id"})
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "articles")
public class Article implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotEmpty
    @Column(nullable = false, length = 1024)
    private String post;

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

    @Column
    private LocalDateTime updatedAt;

Here is a snippet from the controller:

@PutMapping("{id}")
public ResponseEntity<ArticleResponseDTO> update(
        @PathVariable("id") Article fromDb,
        @Valid @RequestBody Article article
) {
    var updated = articlesService.updateArticle(fromDb, article);
    return ResponseEntity.ok(ArticleMapper.toDto(updated));
}

And appropriate service method:

@Transactional
public Article updateArticle(Article fromDb, Article article) {
    fromDb.setPost(article.getPost());
    return fromDb;
}

Retrieving an entity from DB and passing a new one works fine.

In my opinion, when the entity is loaded and modified (setting new value here) at the end session (flush is invoked) dirty check should check the entity state -> find that it is modified -> store new state to DB. With this purpose @Transactional is added.

However, it doesn't store modified entity in DB. Even though the updated DTO is finally returned.

When using repo.save(fromDb) it works fine.

What is the reason for this behaviour?
Is it possible to use dirty checking here?

Here is a link to the example: ArticleService.
It is Spring boot app built with Maven and 21 JDK.


Solution

  • The variable name of fromDb is misleading. fromDb is passed as a parameter to the endpoint, which makes fromDb an unmanaged entity, and requires the use of the save method.

    When updating, retrieve the existing entity from the repository, and update that existing (managed) entity. For example:

    @Transactional
    public Article updateArticle(long articleId, Article source) {
       final var target = repository.getReferenceById(articleId);
         target.setPost(source.getPost());
        return target;
    }