admin管理员组

文章数量:1399347

Could someone please explain how to get Optimistic Locking in JPA to work? My initial idea was, that once I'm passing an entity with Version number other than it is stored in the database the commit is rejected and OptimisticLockException is thrown. My Database in the backend is Oracle 21C

as shown in the code below I'm explicitly setting fixed version number for each HTTP PUT(update) request. But every request is successful. The updateDateTime property is correctly managed by JPA and it is updated with every request. The same for version property. Its value is being incremented on every commit.

Here is my test entity, with property annotated with @Version:

@Entity
@Table(name = "docs", uniqueConstraints = @UniqueConstraint(columnNames = "doc_id"))
public class Doc {

    @Id
    @Column(name = "doc_id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "doc_generator")
    @SequenceGenerator(name = "doc_generator", sequenceName = "doc_seq", allocationSize = 1)
    private Long id;

    @Column(name = "param_1", nullable = false)
    private String param1;

    @Column(name="param_2")
    private String param2;

    @CreationTimestamp
    @Column(name = "created", nullable = false, updatable = false)
    private LocalDateTime createDateTime;

    @UpdateTimestamp
    @Column(name = "updated", nullable = false, updatable = true)
    private LocalDateTime updateDateTime;

    @Version
    @Column(name = "version")
    private Integer version;

    // ...
}

simple repository:

public interface DocRepository extends JpaRepository<Doc, Long> {

    @Query(value = "SELECT d FROM Doc d WHERE d.id = ?1")
    @Lock(LockModeType.OPTIMISTIC)
    Optional<Doc> findByIdWithLock(Long id);
}

a service:

@Service
public class DocService {

    private final DocRepository docRepository;
    private final EntityManager entityManager;
    private final DocMapper docMapper;

    public DocService(DocRepository docRepository, EntityManager entityManager, DocMapper docMapper) {
        this.docRepository = docRepository;
        this.entityManager = entityManager;
        this.docMapper = docMapper;
    }

    public DocDto save(DocDto dto) {
        var entity = docMapper.toEntity(dto);
        var saved = docRepository.save(entity);
        return docMapper.toDto(saved);
    }

    @Transactional
    public DocDto update(Long id, DocDto dto) {
        var updated = docRepository.findByIdWithLock(id)
                .map(entity -> docMapper.updateWith(entity, dto))
                .orElseThrow(() -> new EntityNotFoundException("Document not found"));
        var saved = docRepository.save(updated);
        return docMapper.toDto(saved);
    }

    @Transactional
    public List<DocDto> findAll() {
        return docRepository.findAll().stream()
                .map(docMapper::toDto)
                .collect(Collectors.toList());
    }

    public void delete(Long id) {
        var saved = docRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("Document not found"));
        docRepository.delete(saved);
    }

    public Long getCount() {
        return docRepository.count();
    }

}

controller:

@RestController
@RequestMapping(path = "/api/doc")
public class DocController {

    private final DocService docService;

    public DocController(DocService docService) {
        this.docService = docService;
    }

    @PostMapping(path = {"", "/"})
    public ResponseEntity<DocDto> save(@RequestBody DocDto dto) {
        var response = docService.save(dto);
        return ResponseEntity.ok(response);
    }

    @GetMapping(path = {"", "/"})
    public ResponseEntity<List<DocDto>> findAll() {
        var response = docService.findAll();
        return ResponseEntity.ok(response);
    }

    @PutMapping(path = "/{id}")
    public ResponseEntity<DocDto> update(@PathVariable(value = "id") Long id, @RequestBody DocDto dto) {
        var response = docService.update(id, dto);
        return ResponseEntity.ok(response);
    }

    @DeleteMapping(path = "/{id}")
    public ResponseEntity<Void> delete(@PathVariable(value = "id") Long id) {
        docService.delete(id);
        return ResponseEntity.noContent().build();
    }

    @ExceptionHandler(value = {EntityNotFoundException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<ErrorResponse> handleException(EntityNotFoundException exc) {
        // ...
    }
}

and DTO / Entity mapper:

@Component
public class DocMapper {

    public Doc toEntity(DocDto dto) {
        var entity = new Doc()
                .setParam1(dto.getParam1())
                .setParam2(dto.getParam2());
        return entity;
    }

    public Doc  updateWith(Doc entity, DocDto dto) {
        entity.setParam1(dto.getParam1())
              .setParam2(dto.getParam2())
              .setVersion(1);
        return entity;
    }

    public DocDto toDto(Doc entity) {
        var dto = new DocDto()
                .setId(entity.getId())
                .setParam1(entity.getParam1())
                .setParam2(entity.getParam2())
                .setVersion(entity.getVersion());
        return dto;
    }
}

本文标签: javaJPA Version and optimistic locking problemStack Overflow