【问题标题】:Spring boot 2.0.2, using Spring data how do I get message from entity validationSpring boot 2.0.2,使用 Spring 数据如何从实体验证中获取消息
【发布时间】:2018-07-20 16:05:56
【问题描述】:

我正在构建一个 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响应中返回它,但我似乎无法捕获异常并抓取文本。

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

}

最后,我的用户仓库是这样的(spring data);

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

【问题讨论】:

    标签: spring-boot


    【解决方案1】:

    在执行提交时进行验证。 所以听起来你的控制器启动了一个事务,并且提交将在控制器向客户端返回响应时完成。

    所以用catch(ConstraintViolationException e) 包围repository.save(...) 语句将毫无用处。
    您应该创建一个自定义异常处理程序以允许在它发生的任何地方捕获 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:无法提交 JPA 事务;嵌套异常是 javax.persistence.RollbackException: Error while committing the transaction 在 org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:545) 在 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

    【讨论】:

    • 遗憾的是这不起作用,它没有捕获处理异常。我已经更新了我原来的问题以包含调用 save() 方法的服务。
    • 确实如此。您必须解开链接的异常以检索原始异常。我更新解释。我测试过,它对我有用。
    • 很好的解释! @davidxxx,我是 Java 开发人员和 curios 的新手,您是如何发现错误 C 与错误 B 等包装在一起的,是否记录在案?
    • @protrafree 不幸的是,它没有记录在案。由于该代码上的断点,我发现了这一点。我认为这是一个 Spring 启动错误,因为 Spring 的原则是不允许启动 TransactionSystemException 等低级异常。该错误很有可能在 Spring Boot 的最新版本中消失了。
    • 哦,自从我学习 java 以来,我没有使用过断点,它是否有任何关于春季错误如何工作的好资源,比如它是如何触发的,直到它通过 Web 或 REST API 响应显示给客户端? @davidxxx 我想了解更多
    【解决方案2】:

    您的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 以更简洁的方式提出建议。

    【讨论】:

    • 所以您建议在服务层内验证实体?这就是我一直这样做的方式,但我想这次我会尝试一些不同的东西。
    • 我通常有带有“第一行”验证(例如非空、电子邮件模式等)的请求表示对象(例如创建、更新)。然后我将它们映射到 DTO 以转移到域层。域(服务)层执行域验证(例如,引用对象 id 的存在)。最后将数据映射到实体上并保存在存储库中。
    猜你喜欢
    • 1970-01-01
    • 2020-05-26
    • 2020-05-07
    • 2018-01-23
    • 2021-07-17
    • 2018-06-03
    • 2018-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多