In my Spring Boot 3 project with hibernate 6 I am trying to check the a name is not in use before updating (and saving) a entity.
I want to leave the method annotated with @Transactional and I want to be able to pass a message to the UI if the update/save fails.
There is a number of existing answers, but they all have issues. Here is what I have tried:
1. Catch DataIntegtrityException
This way throws a custom exception - the con is that it doesn't work with @Transactional
//@Transactional - doesn't catch exception with @Transactional
public CarCreateDto updateCar(CarCreateDto updateCarDto, Long id) {
Car Car = CarRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("Car with " + id + " not found"));
try {
CarMapper.partialUpdate(updateCarDto, Car);
CarRepository.save(Car);
return CarMapper.toDto(Car);
} catch (DataIntegrityViolationException e) {
throw new CarAlreadyExistsException("Car with name already exist");
}
}
2. Catch DataIntegtrityException with saveAndFlush
Same as above but uses saveAndFlush
and works with @Transactional. Can't find the reference but the con is that saveAndFlush is not good to use.
@Transactional
public CarCreateDto updateCar(CarCreateDto updateCarDto, Long id) {
Car Car = CarRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("Car with " + id + " not found"));
try {
CarMapper.partialUpdate(updateCarDto, Car);
CarRepository.save(Car);
return CarMapper.toDto(Car);
} catch (DataIntegrityViolationException e) {
throw new CarAlreadyExistsException("Car with name already exist");
}
}
3. Run a pre-save check on the database
The con here is a extra call to the database. Also potential for concurrency issues.
@Transactional
public CarCreateDto updateCar(CarCreateDto updateCarDto, Long id) {
Car Car = CarRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("Car with " + id + " not found"));
boolean samename = updateCarDto.getName().equalsIgnoreCase(Car.getName());
if (samename || !samename && !CarRepository.existsByName(updateCarDto.getName())) {
CarMapper.partialUpdate(updateCarDto, Car);
CarRepository.save(Car);
return CarMapper.toDto(Car);
} else {
throw new DatabaseUniqueConstraintException("Car with name " + updateCarDto.getName() + " already exists");
}
}
4. Put the catch on the controller
This just feels wrong - not a controller responsibility to catch DB exceptions.
@PutMapping(value = "/{id}", consumes = "application/json")
ResponseEntity<CarCreateDto> updateCarDto(@Valid @RequestBody CarCreateDto updateCar, @PathVariable Long id) {
System.out.println("wait");
try {
CarCreateDto Car = CarService.updateCar(updateCar, id);
return new ResponseEntity<>(Car, HttpStatus.OK);
} catch (DataIntegrityViolationException e) {
throw new CarAlreadyExistsException("Car with name already exist");
}
}
So what is the correct way to handle this exception for a uniqueness check?
!samename && !CarRepository.existsByName(updateCarDto.getName())
uses a @Query existsByName to check and return boolean. – Usage