Reputation: 5453
I've been using Hibernate Validator in my Spring project. I'm about to validate my JUser
Object automatically. i.e, I want Spring to validate the Object and set errors in BindigResult. But It doesn't work.
<properties>
<spring.version>4.3.5.RELEASE</spring.version>
<spring.security.version>4.0.2.RELEASE</spring.security.version>
<hibernate.version>4.3.11.Final</hibernate.version>
<validation-api.version>1.1.0.Final</validation-api.version>
<hibernate-validator.version>5.4.0.Final</hibernate-validator.version>
</properties>
....
...
<tx:annotation-driven transaction-manager="hibernateTransactionManager"/>
<context:annotation-config />
<context:component-scan base-package="my.project.controller" />
<mvc:annotation-driven validator="validator">
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
</bean>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator"/>
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="en" />
</bean>
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
@Entity
public class JUser implements Officeable {
@Id
private Long id;
@Column(unique = true, nullable = false)
private String username;
private String password;
@NotEmpty
private String firstName;
@NotEmpty
private String lastName;
private String tel;
}
import javax.validation.ConstraintViolationException;
....
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String update2(HttpServletRequest request, Model model, @ModelAttribute("user") @Valid JUser user, BindingResult result) {
if (!result.hasErrors()) {
System.out.println("binding result has no errors for user ");
try {
JUser updated = userService.update(user);
model.addAttribute("user", updated);
} catch (MessageException | DataIntegrityViolationException ex) {
result.reject("user", ex.getMessage());
} catch (ConstraintViolationException cvex) {
for (ConstraintViolation cv : cvex.getConstraintViolations()) {
result.rejectValue(cv.getPropertyPath().toString(),cv.getMessageTemplate() , cv.getMessage());
}
}
}
return "user/manage";
}
As you see in the above controller method I want Spring to validate the user
Object and set errors in BindigResult
. But It does not work.
For example when user
has empty firstName
I face the output:
output:
binding result has no errors for user
and I have to catch hibernate thrown exceptions by hand:
ConstraintViolationException: may not be empty ...
more description. I've used String @Validated
annotation and It did not work as well. I've read more than ten related stackoverflow questions and they didn't solved my problem.
Upvotes: 7
Views: 3123
Reputation: 6954
If you are using HibernateValidator you must tell to use the HibernateValidator class
By looking the LocalValidatorFactoryBean javadoc
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway. This can also be injected directly into any target dependency of type Validator!
So you should use the setProviderClass method in order to specify what class to use
Here it's what I did (i'm using annotation based config but it's the same):
WebMvcConfig
@Override
public Validator getValidator() {
LocalValidatorFactoryBean lvfb = new LocalValidatorFactoryBean();
lvfb.setProviderClass(HibernateValidator.class);
return lvfb;
}
Model:
@Entity
@Table(name = "CANDIDATO")
public class Candidato extends AbstractModel {
private static final long serialVersionUID = -5648780121365553697L;
.
.
.
private String corsoLaurea;
.
.
.
@Column(name="CORSO_LAUREA", nullable=true)
@NotEmpty
public String getCorsoLaurea() {
return corsoLaurea;
}
}
controller method
@RequestMapping(method = { RequestMethod.PUT }, value = { "/salvaModificheCandidato" })
public ResponseEntity<BaseResponse<String>> modificaCandidato(@RequestBody @Valid ModificaCandidatoDto dto, BindingResult bindResult) throws Exception
{
BaseResponse<String> result = null;
HttpStatus status = null;
try
{
this.candidatoSvc.modificaCandidato(dto);
result = new BaseResponse<String>();
status = HttpStatus.OK;
result.setDescrizioneOperazione("Aggiornamento candidato terminato correttamente");
result.setEsitoOperazione(status.value());
result.setPayload(Collections.EMPTY_LIST);
}
catch (Exception e)
{
result = new BaseResponse<String>();
status = HttpStatus.INTERNAL_SERVER_ERROR;
String message = "Errore nella modifica del candicato con ID "+dto.getIdCandidato()+"; "+e.getMessage();
logger.error(message, e);
result.setDescrizioneOperazione(message);
result.setEsitoOperazione(status.value());
}
return new ResponseEntity<BaseResponse<String>>(result, status);
}
With this configuration I find in bindinresult errors for both the DTO and the Model
I hope this can be useful
EDITED PART
I saw that your issue is to have the bindingresult not empty when you try to persist your object; I changed my code in this way
No change to the model (I used the hibernate validation NotEmpty annotation)
I changed my service method in this way:
@Override
@Transactional(transactionManager = "hibTx", rollbackFor = CandidatiDbException.class, readOnly = false)
public void modificaCandidato(ModificaCandidatoDto dto, BindingResult brErrors) throws CandidatiDbException {
try
{
dao.modificaCandidato(dto, brErrors);
} catch (Exception e)
{
String message = "Errore nella modifica del candidato con ID "+dto.getIdCandidato()+"; "+e.getMessage();
logger.error(message, e);
throw new CandidatiDbException(message);
}
}
As you can see I passed the BindingResult
object to the method
Then I changed my DAO impl in this way:
public class CandidatoDaoImpl<T> implements ICandidatoDao<T> {
@Autowired
@Qualifier("candValidator")
Validator validator;
public void modificaCandidato(ModificaCandidatoDto dto, BindingResult brErrors) {
Session sessione = getSession();
sessione.setCacheMode(CacheMode.IGNORE);
Candidato candidato = sessione.load(Candidato.class, dto.getIdCandidato());
.
.
.
validator.validate(candidato, brErrors);
if( !brErrors.hasErrors() )
{
sessione.saveOrUpdate(candidato);
}
}
}
Finally I updated my WebMvcConfig in this way:
@Configuration
@EnableWebMvc
@Import(SharedSpringConfig.class)
@PropertySource( value={"classpath:configuration.properties"}, encoding="UTF-8", ignoreResourceNotFound=false)
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Bean(name="candValidator")
public Validator validator()
{
LocalValidatorFactoryBean lvfb = new LocalValidatorFactoryBean();
lvfb.setProviderClass(HibernateValidator.class);
return lvfb;
}
@Override
public Validator getValidator() {
return validator();
}
}
In this way when I have some error on the object I want to persist I have the BindingResult object not empty and no exception is raised
I hope this can be useful
Angelo
Upvotes: 1
Reputation: 3106
You can use the ExceptionHandler approach. Just add this method in your controller class. I haven't tested this with the @ModelAttribute
although it should work, I know for sure that it works with @RequestBody
.
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
// your own custom error dto class
ErrorDTO errorDto = constructErrors(fieldErrors);
return errorDto;
}
Upvotes: 1
Reputation: 3085
As per spring-mvc-4.3.xsd
The bean name of the Validator that is to be used to validate Controller model objects. This attribute is not required, and only needs to be specified if a custom Validator needs to be configured. If not specified, JSR-303 validation will be installed if a JSR-303 provider is present on the classpath.
I don't see you wrote any custom validator so you can change
<mvc:annotation-driven validator="validator">
to support the default JSR-303
<mvc:annotation-driven />
Example: Spring 3 MVC and JSR303 @Valid example
Update 1
Could you also try removing validation-api.version
This transitively pulls in the dependency to the Bean Validation API (javax.validation:validation-api:1.1.0.Final).
Upvotes: 1
Reputation: 7189
First thing, can you test if validate is working after adding below code?
pom.xml
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
@Bean // in configuration
public Validator validator() {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
return validatorFactory.getValidator();
}
@Autowired //in controller
private Validator validator;
public <T> void validate(T t) {
Set validate = this.validator.validate(t);
if(!validate.isEmpty()) {
throw new RuntimeException();
}
}
If this works, then can suggest you further to simplify it.
Upvotes: 1