Spring Boot 2.0.2,使用Spring数据如何从实体验证中获取消息

时间:2018-07-20 16:05:56

标签: spring-boot

我有一个正在构建的Spring Boot 2.0.2 Web服务,一个实体中有多个字段,我不想为空。当尝试使用无效字段保留实体时,如何从该特定字段中获取消息?

例如,我有一个实体;

@Entity
@Table(name="users")
public class User {

    @Column(name="password", columnDefinition = "VARCHAR(250)", nullable = false, length = 250)
    @NotNull(message = "Missing password")
    private String password;
}

我有一个服务类,它试图创建一个新用户。尝试创建缺少密码的用户时,将引发异常;

2018-07-20 17:03:33.195 ERROR 78017 --- [nio-8080-exec-1] o.h.i.ExceptionMapperStandardImpl        : HHH000346: Error during managed flush [Validation failed for classes [com.nomosso.restapi.models.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='Missing password', propertyPath=password, rootBeanClass=class com.nomosso.restapi.models.User, messageTemplate='Missing password'}
]]
2018-07-20 17:03:33.215 ERROR 78017 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction] with root cause

javax.validation.ConstraintViolationException: Validation failed for classes [com.nomosso.restapi.models.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='Missing password', propertyPath=password, rootBeanClass=class com.nomosso.restapi.models.User, messageTemplate='Missing password'}
]

我想获取messageTemplate的值,以便可以处理它并在API响应中返回它,但是我似乎无法捕获Exception并获取文本。

当前,API响应如下:

{
    "timestamp": 1532102613231,
    "status": 500,
    "error": "Internal Server Error",
    "message": "Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction",
    "path": "/user"
}

这对服务的用户完全没有帮助。我希望得到这样的答复;

{
        "timestamp": 1532102613231,
        "status": 400,
        "error": "Bad request",
        "message": "Missing password",
        "path": "/user"
    }

我能够生成自己的错误响应,但为此,我需要从无效实体中获取消息。

更新: 这是试图保留实体的服务;

@Service
public class UserService implements UserDetailsService {

    private UserRepository userRepository;

    private PasswordEncoder passwordEncoder;

    @Autowired
    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    /**
     * Creates a new user account
     * @param email email address
     * @param password unencoded password
     * @param firstname firstname
     * @param lastname lastname
     * @param displayName display name
     * @return new User
     */
    public User createUser(String email, String password, String firstname, String lastname, String displayName) {
        User user = new User();
        user.setEmail(email);
        user.setFirstname(firstname);
        user.setLastname(lastname);
        user.setDisplayName(displayName);
        if (password != null && !password.isEmpty()) {
            user.setPassword(passwordEncoder.encode(password));
        }
        user.setEmailVerified(false);
        user.setCreatedAt(new Date());
        user.setUpdatedAt(user.getCreatedAt());

        userRepository.save(user);

        return user;
    }

}

最后,我的用户存储库看起来像这样(弹簧数据);

public interface UserRepository extends CrudRepository<User, String> {
}

2 个答案:

答案 0 :(得分:1)

验证在执行提交时发生。 因此,听起来您的控制器已开始事务,并且将在控制器将响应返回给客户端时完成提交。

因此,用repository.save(...)包围catch(ConstraintViolationException e)语句将毫无用处。
您应该创建一个自定义的异常处理程序,以允许在发生的任何地方捕获ConstraintViolationException
但是您无法直接捕获它,因为Spring将其包装到org.springframework.transaction.TransactionSystemException中。

实际上是准确的: 1)javax.validation.ConstraintViolationException由2)javax.persistence.RollbackException: Error while committing the transaction包裹,而自身{3} org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction

这是更高级别的异常:

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:545)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
   ....

因此您可以通过以下方式编写ControllerAdvice:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

    Logger LOGGER = LoggerFactory.getLogger(MyExceptionHandler.class);

    @ExceptionHandler(value = { TransactionSystemException.class })
    protected ResponseEntity<Object> handleConflict(TransactionSystemException ex, WebRequest request) {
        LOGGER.error("Caught", ex);
        Throwable cause = ex.getRootCause();

        if (cause instanceof ConstraintViolationException) {
           Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) cause).getConstraintViolations();
           // iterate the violations to create your JSON user friendly message
           String msg = ...;
           return handleExceptionInternal(ex, msg , new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
      }
    }        

}

需要使用TransactionSystemException.getRootCause()来检索原始引发的异常:在您的情况下为ConstraintViolationException

答案 1 :(得分:0)

您的createUser方法不正确。如果您知道密码字段是必填字段(nullable = false),那么如果缺少密码值(对我来说这是一种代码味道),则无法继续创建实体:

 if (password != null && !password.isEmpty()) {
        user.setPassword(passwordEncoder.encode(password));
    }

您应该抛出自定义异常,例如:

else{
    throw new NoRequiredFieldException("password"); //customize exception to add required informations
}

然后您可以使用@ControllerAdvice作为@davidxxx提议以更干净的方式。