@ Check не поможет вам в этом случае, так как он не предназначен для запроса базы данных как части проверки перед сохранением объекта. Как и другие аннотации валидатора спящего режима, он предназначен для базовой c проверки ограничений.
Таким образом, единственный способ - выполнить проверку БД либо перед сохранением, либо как часть настраиваемой аннотации . Однако, поскольку это будет дорого проверять БД каждый раз, вы можете избежать этого, имея кеш в памяти (если развернут только 1 экземпляр вашего приложения) или распределенный кеш (если несколько экземпляров) .
Пример использования пользовательских аннотаций и кеша в памяти:
(Версия Spring Boot: 2.3.1.RELEASE, валидатор Hibernate: 5.2.4.Final) ( Если вы хотите использовать кеш в памяти, не забудьте сделать его недействительным, когда будет удалена запись с ролью ROLE_ ROOT )
- Класс сущности - (идентификатор автоинкремента)
@Entity
@Table(name = "my_table")
@CheckRole
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "role")
private String role;
@Column(name = "user_id")
private int userId;
public int getId() {
return id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
}
Репозиторий -
@Repository
public interface MyTableRepository extends JpaRepository<MyEntity, Integer> {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Query(value = "SELECT CASE WHEN COUNT(e) > 0 THEN true ELSE false END FROM MyEntity e WHERE e.role = :roleName")
Boolean checkIfRoleExists(@Param("roleName") String roleName);
}
Аннотация -
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = RoleValidator.class)
public @interface CheckRole {
String message() default "Cannot have duplicate entry for Role: ROLE_ROOT";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Валидатор ограничений Impl (Используемый кэш кофеина в памяти) -
public class RoleValidator implements ConstraintValidator<CheckRole, MyEntity > {
private static final String ROLE_TO_VALIDATE = "ROLE_ROOT";
private LoadingCache<String, Boolean> myCache;
public RoleValidator(MyTableRepository repository) {
myCache = Caffeine.newBuilder()
.maximumSize(1)
.expireAfterWrite(5, TimeUnit.MINUTES)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(repository::checkIfRoleExists);
}
@Override
public void initialize(CheckRole constraintAnnotation) {
}
@Override
public boolean isValid(MyEntity entity, ConstraintValidatorContext context) {
String roleValue = entity.getRole();
if (roleValue.equals(ROLE_TO_VALIDATE)) {
boolean isValid = !myCache.get(ROLE_TO_VALIDATE);
if (!isValid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Cannot have duplicate entry for Role: " + ROLE_TO_VALIDATE)
.addConstraintViolation();
}
return isValid;
} else {
return true;
}
}
}
Настройщик свойств гибернации -
@Component
public class ValidatorAddingCustomizer implements HibernatePropertiesCustomizer {
private final ObjectProvider<Validator> provider;
@Autowired
public ValidatorAddingCustomizer(ObjectProvider<Validator> provider) {
this.provider = provider;
}
@Override
public void customize(Map<String, Object> hibernateProperties) {
Validator validator = provider.getIfUnique();
if (validator != null) {
hibernateProperties.put("javax.persistence.validation.factory", validator);
}
}
}
Проверка в действии -
@RestController
public class MyController {
@Autowired
private MyTableRepository repository;
@GetMapping("/hello")
public void hello() {
MyEntity myEntity = new MyEntity();
myEntity.setRole("ROLE_ROOT");
myEntity.setUserId(3);
repository.save(myEntity); //saves successfully
MyEntity myEntity2 = new MyEntity();
myEntity2.setRole("ROLE_ROOT");
myEntity2.setUserId(4);
repository.save(myEntity2); //Throws Constraint Violation Exception
}
}