Я создал небольшой примерный проект, чтобы показать две проблемы, с которыми я сталкиваюсь при настройке проверки Spring Boot и ее интеграции с Hibernate. Я уже пробовал другие ответы, которые я нашел по этой теме, но, к сожалению, они не сработали для меня или в которых предлагалось отключить проверку Hibernate.
Я хочу использовать специальный валидатор, реализующий ConstraintValidator<ValidUser, User>
, и вставить в него свой UserRepository
. В то же время я хочу сохранить поведение Hibernate по умолчанию, которое проверяет наличие ошибок проверки во время обновления / сохранения.
Пишу сюда для полноты картины основные разделы приложения.
Пользовательская конфигурация В этом классе я установил пользовательский валидатор с пользовательским MessageSource
, поэтому Spring будет читать сообщения из файла resources/messages.properties
@Configuration
public class CustomConfiguration {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
}
Компонент. Ничего особенного, если не пользовательский валидатор @ValidUser
@ValidUser
@Entity
public class User extends AbstractPersistable<Long> {
private static final long serialVersionUID = 1119004705847418599L;
@NotBlank
@Column(nullable = false)
private String name;
/** CONTACT INFORMATION **/
@Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String landlinePhone;
@Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String mobilePhone;
@NotBlank
@Column(nullable = false, unique = true)
private String username;
@Email
private String email;
@JsonIgnore
private String password;
@Min(value = 0)
private BigDecimal cashFund = BigDecimal.ZERO;
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLandlinePhone() {
return landlinePhone;
}
public void setLandlinePhone(String landlinePhone) {
this.landlinePhone = landlinePhone;
}
public String getMobilePhone() {
return mobilePhone;
}
public void setMobilePhone(String mobilePhone) {
this.mobilePhone = mobilePhone;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public BigDecimal getCashFund() {
return cashFund;
}
public void setCashFund(BigDecimal cashFund) {
this.cashFund = cashFund;
}
}
Пользовательский валидатор Вот где я пытаюсь внедрить репозиторий. Репозиторий всегда имеет значение null, если не когда я отключаю проверку Hibernate.
public class UserValidator implements ConstraintValidator<ValidUser, User> {
private Logger log = LogManager.getLogger();
@Autowired
private UserRepository userRepository;
@Override
public void initialize(ValidUser constraintAnnotation) {
}
@Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
User foundUser = userRepository.findByUsername(value.getUsername());
if (foundUser != null && foundUser.getId() != value.getId()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{ValidUser.unique.username}").addConstraintViolation();
return false;
}
} catch (Exception e) {
log.error("", e);
return false;
}
return true;
}
}
messages.properties
#CUSTOM VALIDATORS
ValidUser.message = I dati inseriti non sono validi. Verificare nuovamente e ripetere l'operazione.
ValidUser.unique.username = L'username [${validatedValue.getUsername()}] è già stato utilizzato. Sceglierne un altro e ripetere l'operazione.
#DEFAULT VALIDATORS
org.hibernate.validator.constraints.NotBlank.message = Il campo non può essere vuoto
# === USER ===
Pattern.user.landlinePhone = Il numero di telefono non è valido. Dovrebbe essere nel formato E.123 internazionale (https://en.wikipedia.org/wiki/E.123)
В моих тестах вы можете попробовать из исходного кода, у меня две проблемы:
- Внедренный репозиторий внутри
UserValidator
имеет значение null, если я не отключу проверку Hibernate (spring.jpa.properties.javax.persistence.validation.mode = none) - Даже если я отключу валидатор Hibernate, мои тестовые примеры не пройдут, потому что что-то мешает Spring использовать интерполяцию строк по умолчанию для сообщений проверки, которые должны быть чем-то вроде [Constraint]. [Имя класса в нижнем регистре]. [PropertyName]. Я не хочу использовать аннотацию ограничения с элементом значения, подобным этому
@NotBlank(message="{mycustom.message}")
, потому что я не вижу смысла, учитывая, что у него есть собственная конвенция для интерполяции, и я могу воспользоваться этим ... это означает меньше кодирования.
прилагаю код; вы можете просто запустить тесты Junit и увидеть ошибки (проверка Hibernate включена, проверьте application.properties).
Что я делаю неправильно? Что я мог сделать, чтобы решить эти две проблемы?
====== ОБНОВЛЕНИЕ ======
Чтобы уточнить, прочтите документацию по проверке Spring https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints они говорят:
По умолчанию LocalValidatorFactoryBean настраивает SpringConstraintValidatorFactory, который использует Spring для создания экземпляров ConstraintValidator. Это позволяет вашим настраиваемым ConstraintValidators извлекать выгоду из внедрения зависимостей, как и любой другой компонент Spring.
Как видите, реализация ConstraintValidator может иметь зависимости @Autowired, как и любой другой компонент Spring.
В моем классе конфигурации я создавал свои LocalValidatorFactoryBean
по мере их написания.
Еще один интересный вопрос: this и это, но мне с ними не повезло.
====== ОБНОВЛЕНИЕ 2 ======
После долгих исследований кажется, что с валидатором Hibernate инъекция не предусмотрена.
Я нашел несколько способов сделать это:
1-й способ
Создайте этот класс конфигурации:
@Configuration
public class HibernateValidationConfiguration extends HibernateJpaAutoConfiguration {
public HibernateValidationConfiguration(DataSource dataSource, JpaProperties jpaProperties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers);
}
@Autowired
private Validator validator;
@Override
protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
super.customizeVendorProperties(vendorProperties);
vendorProperties.put("javax.persistence.validation.factory", validator);
}
}
2-й способ
Создать служебный компонент
@Service
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
а затем в инициализации валидатора:
@Override
public void initialize(ValidUser constraintAnnotation) {
userRepository = BeanUtil.getBean(UserRepository.class);
em = BeanUtil.getBean(EntityManager.class);
}
очень важно
В обоих случаях, чтобы заставить его работать, вам нужно «перезагрузить» диспетчер сущностей следующим образом:
@Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
em.setFlushMode(FlushModeType.COMMIT);
//your code
} finally {
em.setFlushMode(FlushModeType.AUTO);
}
}
В любом случае, я не знаю, действительно ли это безопасный способ. Скорее всего, доступ к уровню сохраняемости вообще не является хорошей практикой.
ConstraintValidator
с использованиемCrudRepository
. Удалось ли вам решить эту @drenda? - person Gideon   schedule 05.08.2020