简体   繁体   中英

Spring Web MVC validation by Hibernate Validator doesn't draw Errors in BindingResult

I've been using Hibernate Validator in my Spring project. I'm about to validate my JUser Object automatically. ie, I want Spring to validate the Object and set errors in BindigResult. But It doesn't work.

pom.xml

<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>
....

applicationContext.xml

...
<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>

JUser.java

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;
}

UserController.java

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.

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.

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

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).

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM