【问题标题】:Hibernate - Is there any way to get the name of the column for which a Constraint was violated?Hibernate - 有没有办法获取违反约束的列的名称?
【发布时间】:2020-08-16 17:22:45
【问题描述】:

我正在使用 Hibernate 将实体保存在数据库中。

在Entity字段(列)上,我定义了很多Constraints(比如NotNull或者length=10)。 当违反约束时,将引发 ConstraintViolationException。我希望能够以某种方式跟踪违反了哪一列的约束。 Java 库“javax.validation.ConstraintViolation”中的 ConstraintViolationException 有一个方法“getConstraintViolations()”,所以我可以这样做

constraintViolationException.getConstraintViolations().getPropertyPath()

这应该给我列名。

但是,Hibernate 抛出的 ConstraintViolationException 没有方法“getConstraintViolations()”,并且似乎没有类似的方法。 我现在唯一的选择是分析调用“getConstraintName()”返回的字符串:

constraintViolationException.getConstraintName()

但这似乎有点混乱且不安全。

有人知道如何获取列名吗?

编辑:

pom.xml里面的相关依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

【问题讨论】:

  • 消息选项? @Email(message = "Invalid email") @NotEmpty(message = "Please enter email") private String email;violation.getMessage()
  • 我想知道你是如何进入休眠状态的ConstraintViolationException。您能否提供一个配置的最小示例,例如pom.xmlbuild.gradle?
  • @DirkDeyne 我用@Column() 注释了我的大部分类字段。我在那里没有找到类似于“消息”的内容:/ 更改注释以完成此操作是否更好?
  • 这可能对hibernate-notnull-vs-nullable有帮助?
  • @weirdVariable 您无法在访问数据库之前控制数据的某些约束(例如@NotNull),但您无法确定在访问数据库之前是否会违反唯一键约束。所以不要期望得到一个javax.validation.ConstraintViolation 来满足你的所有约束。

标签: java hibernate spring-boot maven


【解决方案1】:

你在混合东西。

Bean 验证

Bean Validation API 允许您使用 @NotNull@Size 以及 javax.validation.constraints 包的所有其他注释来验证您的 bean。

您可以使用Validator 来验证您的bean,并获得带有属性信息的所有ConstraintViolationjavax.validation.ConstraintViolationException

JPA/Hibernate 支持Bean Validation API。如果persistence.xml 中的&lt;validation-mode&gt; 元素设置为AUTO(或未设置,因为AUTO 是默认值),JPA 将在使用Validator 保存实体之前验证实体,并抛出@ 987654334@ 如果有任何 ConstraintViolations,仅当环境中存在 Bean Validation 的实现时。如果将&lt;validation-mode&gt; 设置为CALLBACK,JPA 将始终验证实体,如果环境中没有Bean Validation 实现,JPA 将在部署期间抛出错误。

SQL 约束

JPA 注释,如@Column 提供生成数据库 DDL 的信息。使用@Column(nullable = false, length = 10) 将生成一个最大长度为10 的数据库列和一个NOT NULL SQL 约束。但就是这样。在将语句发送到数据库之前,Hibernate 不会检查这一点。

如果提供了 null 值,数据库将报告 SQL 错误,该错误将由 JDBC 驱动程序映射到 SQLExceptionSQLException 中的错误消息对于每个 DBMS 会有所不同,但会包含约束名称。 Hibernate 将此异常包装在 org.hibernate.exception.ConstraintViolationException 中,该 org.hibernate.exception.ConstraintViolationException 从 SQLException 消息中提取约束名称。

结论

  • 在您的persistence.xml 上将&lt;validation-mode&gt; 设置为CALLBACK,以确保始终执行验证并且无效值不会进入数据库
  • 使用javax.validation.constraints注解来处理SQL列约束,例如一个最大大小为50的非空列:

    @NotNull @Size(max = 50) @Column(name = "NAME", nullable = false, length = 50)

  • 为唯一约束和外键约束提供有意义的名称,因为这是唯一可以用来确定违反什么约束的信息。

【讨论】:

  • 谢谢!我按照您的建议做了,使用了匹配的 javax.validation.constraints 注释,并正在使用 Validator 类验证它们。但是,我决定最好自己检查唯一约束和外键约束(在将实体保存到数据库之前),因为其他一切都太麻烦了,还可能导致安全风险。
  • 您可以检查外键,使用entityManager.find 并检查null,或者使用entityManger.getReference 并捕获EntityNotFoundException,但在唯一键的情况下,您肯定可以进行查询在保存您的实体之前,但如果存在并发,这将不会总是有效。
【解决方案2】:

Areus 的 answer 解释说不要混淆 Hibernate 验证器和数据库约束,但在此基础上,从您获得的数据库异常中获取受影响的列名并不总是那么容易。例如,当您对数据库列有唯一约束时,很难将此异常正确地返回给用户。

我所做的是有一个类来处理我的 REST 调用异常处理并找到特定的异常消息。在 Spring/Hibernate 中我还没有找到其他方法。

@ControllerAdvice
@RequestMapping(produces = APPLICATION_JSON_VALUE)
public class RestResponseEntityExceptionHandler {

    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<ErrorDTO> dataIntegrityViolationException(final DataIntegrityViolationException e) {
        String mostSpecificCauseMessage = e.getMostSpecificCause().getMessage();
        if (e.getCause() instanceof ConstraintViolationException) {
            String name = ((ConstraintViolationException) e.getCause()).getConstraintName();
            log.debug("Encountered ConstraintViolationException, details: " + mostSpecificCauseMessage);
            return determineError(mostSpecificCauseMessage, name);
        } else {
            log.debug("Encountered DataIntegrityViolation exception, details: " + mostSpecificCauseMessage);
            ErrorDTO errorDTO =
                new ErrorDTO("BAD_REQUEST_ERROR", "DataIntegrityViolation exception. " + mostSpecificCauseMessage);
            return new ResponseEntity<>(errorDTO, HttpStatus.BAD_REQUEST);
        }
    }

    //this catches the manually invoked entityManager.flush() exceptions
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ErrorDTO> constraintViolationException(final ConstraintViolationException e) {
        String message = e.getMessage();
        String name = e.getConstraintName();
        log.debug("Encountered ConstraintViolationException, details: {}", message);
        return determineError(message, name);
    }

    private ResponseEntity<ErrorDTO> determineError(String message, String name) {
        Map<String, String> errors = new HashMap<>();
        switch (name) {
            case Project.UNIQUE_CONSTRAINT_NAME:
                errors.put("name", ALREADY_EXISTS);
                break;
            case Value.UNIQUE_CONSTRAINT_VALUE:
                errors.put("value", ALREADY_EXISTS);
                break;
            //and so on
            default:
                break;
        }

        if (!errors.isEmpty()) {
            ErrorDTO error = new ErrorDTO(VALIDATION_ERROR, VALIDATION_ERROR_MESSAGE, errors);
            return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
        } else {
            ErrorDTO errorDTO = new ErrorDTO("CONFLICT_ERROR", message);
            return new ResponseEntity<>(errorDTO, HttpStatus.CONFLICT);
        }
    }
}

您还需要在您的模型实体/数据库中定义约束名称,例如:

@Table(uniqueConstraints = {
    @UniqueConstraint(name = UNIQUE_CONSTRAINT_VALUE, columnNames = {"value"})})

public static final String UNIQUE_CONSTRAINT_VALUE = "unique_constraint_property_value";

当然在数据库定义中也是如此。

【讨论】:

    猜你喜欢
    • 2012-07-15
    • 1970-01-01
    • 1970-01-01
    • 2015-11-16
    • 1970-01-01
    • 1970-01-01
    • 2015-10-21
    • 2022-01-26
    相关资源
    最近更新 更多