web-dev-qa-db-fra.com

Spring Boot ne gère pas javax.validation.ConstraintViolationException

J'ai défini un modèle pour valider le courrier électronique dans ma classe d'entité. Dans ma classe de gestionnaires d'exceptions de validation, j'ai ajouté un gestionnaire pour ConstraintViolationException. Mon application utilise SpringBoot 1.4.5.

Profile.Java

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "profile")
public class Profile extends AuditableEntity {

  private static final long serialVersionUID = 8744243251433626827L;

  @Column(name = "email", nullable = true, length = 250)
  @NotNull
  @Pattern(regexp = "^([^ @])+@([^ \\.@]+\\.)+([^ \\.@])+$")
  @Size(max = 250)
  private String email;
....
}

ValidationExceptionHandler.Java

@ControllerAdvice
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {

  private MessageSource messageSource;

  @Autowired
  public ValidationExceptionHandler(MessageSource messageSource) {
    this.messageSource = messageSource;
  }

  @ExceptionHandler(ConstraintViolationException.class)
  public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex,
  WebRequest request) {
    List<String> errors = new ArrayList<String>();
    ....
    }
} 

Lorsque je lance mon code et passe une adresse électronique invalide, j'obtiens l'exception suivante. Le code dans handleConstraintViolation n'est jamais exécuté. Le statut HTTP renvoyé dans l'exception est 500, mais je souhaite en renvoyer 400. Avez-vous une idée de la façon dont je peux atteindre cet objectif?

2017-07-12 22:15:07.078 ERROR 55627 --- [nio-9000-exec-2] o.h.c.s.u.c.UserProfileController        : Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]

javax.validation.ConstraintViolationException: Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]

at  org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.Java:138)

at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.Java:78)    
7
bostonjava

Vous ne pouvez pas attraper ConstraintViolationException.class car il n'est pas propagé à cette couche de votre code, il est attrapé par les couches inférieures, encapsulé et rediffusé sous un autre type. Ainsi, l'exception qui frappe votre couche Web n'est pas une ConstraintViolationException.

Dans mon cas, c'est une TransactionSystemException. J'utilise les annotations @Transactional de Spring avec la JpaTransactionManager. EntityManager lève une exception d'annulation lorsque quelque chose ne va pas dans la transaction, qui est convertie en TransactionSystemException par JpaTransactionManager.

Donc, vous pourriez faire quelque chose comme ça:

@ExceptionHandler({ TransactionSystemException.class })
public ResponseEntity<RestResponseErrorMessage> handleConstraintViolation(Exception ex, WebRequest request) {
    Throwable cause = ((TransactionSystemException) ex).getRootCause();
    if (cause instanceof ConstraintViolationException) {
        Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) cause).getConstraintViolations();
        // do something here
    }
}
12
nimai

Je veux juste ajouter quelque chose. J'essayais de faire la même chose, en validant l'entité. Ensuite, j'ai réalisé que Spring a déjà tout mis hors de la boîte si vous validez l'entrée du contrôleur.

@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid ProfileDto profile){
...    
}

L'annotation @Valid déclenchera la validation avec les annotations javax.validation.

Supposons que vous avez une annotation de modèle sur le nom d'utilisateur de votre profil avec une expression rationnelle n'autorisant pas les espaces.

Spring construira une réponse avec le statut 400 (mauvaise demande) et un corps comme celui-ci:

{
    "timestamp": 1544453370570,
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Pattern.ProfileDto.username",
                "Pattern.username",
                "Pattern.Java.lang.String",
                "Pattern"
            ],
            "arguments": [
                {
                    "codes": [
                        "profileDto.username",
                        "username"
                    ],
                    "arguments": null,
                    "defaultMessage": "username",
                    "code": "username"
                },
                [],
                {
                    "defaultMessage": "^[A-Za-z0-9_\\-.]+$",
                    "arguments": null,
                    "codes": [
                        "^[A-Za-z0-9_\\-.]+$"
                    ]
                }
            ],
            "defaultMessage": "must match \"^[A-Za-z0-9_\\-.]+$\"",
            "objectName": "profileDto",
            "field": "username",
            "rejectedValue": "Wr Ong",
            "bindingFailure": false,
            "code": "Pattern"
        }
    ],
    "message": "Validation failed for object='profileDto'. Error count: 1",
    "path": "/profile"
}
0
Ena

 @ResponseBody
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    @ExceptionHandler(DataIntegrityViolationException.class)
    public Map errorHandler(DataIntegrityViolationException ex) {
        Map map = new HashMap();
        map.put("rs_code", 422);
        map.put("rs_msg", "data existed !");
        return map;
    }

attrapez juste un ami [org.springframework.dao.DataIntegrityViolationException]! Je l'ai corrigé il y a quelques secondes!

0
abai parhat

Je revérifierais que vous avez importé le bon ConstraintViolationException

Celui que vous voulez provient du paquetage org.hibernate.exception.ConstraintViolationException. Si vous avez importé le javax.validation.ConstraintViolationException, il sera ignoré comme vous l'avez expérimenté.

import org.hibernate.exception.ConstraintViolationException;

@RestController
public class FeatureToggleController {

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

Ce sera appelé comme prévu. 

0
Chris Turner

Je pense que vous devriez ajouter @ResponseStatus(HttpStatus.BAD_REQUEST) à votre @ExceptionHandler:

@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
    List<String> errors = new ArrayList<String>();
    ....
}
0
Patrick