kvaq
kvaq

Reputation: 11

Avoid updating entities with JPA based on timestamp column

let's consider two JPA entities A and B :

@Entity
@Table(name = "A")
@SequenceGenerator(name = "seq_a", allocationSize = 50, initialValue = 1)
public class A {
    @Id
    @GeneratedValue(generator = "seq_a", strategy = GenerationType.SEQUENCE)
    @Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
    private Long id;
    @Column(name = "CODE")
    private String code;
    @OneToMany(mappedBy = "a", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    private Set<B> bSet = new HashSet<>();
    @Column(name = "CREATED_TIME")
    private LocalDateTime createdTime;
    //getters + setters
}

@Entity
@Table(name = "B")
@SequenceGenerator(name = "seq_b", allocationSize = 50, initialValue = 1)
public class B {
    @Id
    @GeneratedValue(generator = "seq_b", strategy = GenerationType.SEQUENCE)
    @Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
    private Long id;
    @Column(name = "SOMETHING")
    private String something;
    @ManyToOne(optional = false)
    @JoinColumn(name = "A_ID", nullable = false, updatable = false)
    private A a;
    @Column(name = "CREATED_TIME")
    private LocalDateTime createdTime;
    //getters + setters
}

then consider RestController (springboot context) that have one GET method used for retrieving detail of entity A :

@GetMapping("/{id}")
public ResponseEntity<ADTO> getA(@PathVariable(name = "id", required = true) Long id) {
        return aRepository.findById(id)
                .map(a -> new ResponseEntity<>(mapper.mapToDomain(a), HttpStatus.OK))
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}

method POST used for creating records of A:

@PostMapping
public ResponseEntity<ADTO> addA(@RequestBody @Valid ADTO aDTO) {
        return new ResponseEntity<>(mapper.mapToDomain(a.save(mapper.mapToEntity(ADTO))), HttpStatus.CREATED);
}

and PUT method for updating :

@PutMapping
public ResponseEntity<ADTO> updateA(@RequestBody @Valid ADTO aDTO) {
        A a = aRepository.findById(aDTO.getId()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
        ADTO aDTOfound = mapper.mapToDomain(a);
        BeanUtils.copyProperties(aDTO, aDTOfound);
        return new ResponseEntity<>(mapper.mapToDomain(aRepository.save(mapper.mapToEntity(aDTOfound), HttpStatus.OK)));

}

then let's say that, createdTime attribute is updated everytime the entity is persisted (including created - updating createdTime attribute is done under the hood ...).

Then let's consider scenario, where two users at the same time are retrieving detail of the same entity A (id 1). If user X update the detail from the retrieved content via PUT method, is there any way how to avoid user Y to update the same entity with old content (notice that the createdTime attribute is updated on record with id 1) ? I know that one possible solution, is to make sure that the retrieved createdTime and one from aDTO in update method is the same, but is there any "more" standard solution for this problem ? For example, how to avoid updating entity A (if it was updated previously with USER 1) but let update the childs in Bset which ones for example were not updated by user 1 ...

Upvotes: 1

Views: 322

Answers (1)

Shailesh Chandra
Shailesh Chandra

Reputation: 2340

This is typical problem statement of Optimistic Locking

Optimistic locking is a mechanism that prevents an application from being affected by the "lost update" phenomenon in a concurrent environment while allowing some high degree of concurrency at the same time.

I will solve this problem using @Version, add @Version field in your entity like below

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name = "student_name")
    private String studentName;

    @Column(name = "roll_number")
    private String rollNumber;
    
    @Column(name = "version")
    @Version
    private Long version;
    
} 

In above case When we create an entity for the first time default version value will be zero

On update, the field annotated with @Version will be incremented and subsequent update will same version will fail with OptimisticLockException

The @Version annotation in hibernate is used for Optimistic locking while performing update operation. For more details you can visit https://vladmihalcea.com/jpa-entity-version-property-hibernate/

Upvotes: 1

Related Questions