Hibernate – 有没有办法获取违反约束的列的名称?

huangapple go评论73阅读模式
英文:

Hibernate - Is there any way to get the name of the column for which a Constraint was violated?

问题

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

在实体字段(列)上,我已经定义了许多约束(例如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>
英文:

I'm using Hibernate to save Entities in a Database.

On the Entity fields (columns), I have defined a lot of Constraints (such as NotNull or length=10).
When a Constraint is violated, a ConstraintViolationException is thrown. I want to somehow be able to track for which column a constraint was violated.
The ConstraintViolationException in the Java-Library "javax.validation.ConstraintViolation" has a method "getConstraintViolations()" so I can just do

> constraintViolationException.getConstraintViolations().getPropertyPath()

which should give me the column name.

However, the ConstraintViolationException thrown by Hibernate does not have a method "getConstraintViolations()" and there doesn't seem to be anything like it.
My only option now would be to analyze the String returned by calling "getConstraintName()":

> constraintViolationException.getConstraintName()

But this seems kind of messy and not secure.

Does anyone have an idea how I can get the column name?

EDIT:

Relevant dependencies inside the pom.xml:

        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.h2database&lt;/groupId&gt;
            &lt;artifactId&gt;h2&lt;/artifactId&gt;
        &lt;/dependency&gt;

答案1

得分: 3

你正在混合使用不同的东西。

Bean验证

Bean验证 API 允许您验证您的bean,使用 javax.validation.constraints 包中的 @NotNull@Size 和其他所有注释。

您可以使用 Validator 来验证您的bean,并获得一个包含属性信息的 javax.validation.ConstraintViolationExceptionConstraintViolation

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

SQL约束

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

如果提供了一个null值,数据库将报告一个SQL错误,该错误将由JDBC驱动程序映射为 SQLExceptionSQLException 中的错误消息对于每个DBMS都是不同的,但将包含约束名称。Hibernate将这些异常封装在 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)

  • 为唯一约束和外键约束提供有意义的名称,因为这将是您唯一可以获取以识别违反的约束的信息。

英文:

You are mixing things.

Bean Validation

The Bean Validation API lets you validate your beans, with the @NotNull, @Size, and all the other annotations of the javax.validation.constraints package.

You can use a Validator to validate your beans and get a javax.validation.ConstraintViolationException with all the ConstraintViolation with the info of the property.

JPA/Hibernate supports the Bean Validation API. If the &lt;validation-mode&gt; element in persistence.xml is set to AUTO (or it's not set, as AUTO is the default value), JPA will validate entities prior to saving them with a Validator, and throw a javax.validation.ConstraintViolationException if there are any ConstraintViolations, only if an implementation of Bean Validation is present on the environment. If you set &lt;validation-mode&gt; to CALLBACK, JPA will always validate the entities and will throw an error during deployment if there is not a Bean Validation implementation on the environment.

SQL Constraints

JPA annotations, like @Column provide information to generate your database DDL. Using @Column(nullable = false, length = 10) will generates a database column with a maximum length of 10 and a NOT NULL SQL constraint. But that's it. Hibernate don't check this before sending the statement to the database.

If a null value is provided, the database will report an SQL Error, that will be mapped to a SQLException by the JDBC driver. The error message in the SQLException will be different for each DBMS, but will contain the constraint name. Hibernate wraps this exceptions in a org.hibernate.exception.ConstraintViolationException that extracts the constraint name from the SQLException message.

Conclusion

  • Set &lt;validation-mode&gt; to CALLBACK on your persistence.xml to make sure validation is always performed and invalid values don't hit the database

  • Use javax.validation.constraints annotations to mach the SQL column constraints, for example, a not null column with a max size of 50:

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

  • Provide meaningful names for unique constraints and foreign key constraints, as this will be the only info you can get to identify what constraint is violated.

答案2

得分: 1

// 代码部分不要翻译

// 这是一个用于处理 REST 调用异常的类,它找到特定的异常消息。
@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";

// 当然,在数据库定义中也需要。

注意:这里只提供了代码部分的翻译,将其中的代码从英文翻译为中文。如果你有关于这段代码的问题或需要进一步解释,请随时提问。

英文:

The answer by Areus explains to not confuse Hibernate validator and the database constraints, but building on that, it's not always easy to fetch the affected column name from the database exception that you get. For instance when you have a unique constraint on the database column it's hard to properly return this exception to the user.

What I did was have a class that handles my REST call exception handling and finds that specific exception message. I found no other way to do this yet in Spring/Hibernate.

@ControllerAdvice
@RequestMapping(produces = APPLICATION_JSON_VALUE)
public class RestResponseEntityExceptionHandler {
@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity&lt;ErrorDTO&gt; dataIntegrityViolationException(final DataIntegrityViolationException e) {
String mostSpecificCauseMessage = e.getMostSpecificCause().getMessage();
if (e.getCause() instanceof ConstraintViolationException) {
String name = ((ConstraintViolationException) e.getCause()).getConstraintName();
log.debug(&quot;Encountered ConstraintViolationException, details: &quot; + mostSpecificCauseMessage);
return determineError(mostSpecificCauseMessage, name);
} else {
log.debug(&quot;Encountered DataIntegrityViolation exception, details: &quot; + mostSpecificCauseMessage);
ErrorDTO errorDTO =
new ErrorDTO(&quot;BAD_REQUEST_ERROR&quot;, &quot;DataIntegrityViolation exception. &quot; + mostSpecificCauseMessage);
return new ResponseEntity&lt;&gt;(errorDTO, HttpStatus.BAD_REQUEST);
}
}
//this catches the manually invoked entityManager.flush() exceptions
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity&lt;ErrorDTO&gt; constraintViolationException(final ConstraintViolationException e) {
String message = e.getMessage();
String name = e.getConstraintName();
log.debug(&quot;Encountered ConstraintViolationException, details: {}&quot;, message);
return determineError(message, name);
}
private ResponseEntity&lt;ErrorDTO&gt; determineError(String message, String name) {
Map&lt;String, String&gt; errors = new HashMap&lt;&gt;();
switch (name) {
case Project.UNIQUE_CONSTRAINT_NAME:
errors.put(&quot;name&quot;, ALREADY_EXISTS);
break;
case Value.UNIQUE_CONSTRAINT_VALUE:
errors.put(&quot;value&quot;, ALREADY_EXISTS);
break;
//and so on
default:
break;
}
if (!errors.isEmpty()) {
ErrorDTO error = new ErrorDTO(VALIDATION_ERROR, VALIDATION_ERROR_MESSAGE, errors);
return new ResponseEntity&lt;&gt;(error, HttpStatus.BAD_REQUEST);
} else {
ErrorDTO errorDTO = new ErrorDTO(&quot;CONFLICT_ERROR&quot;, message);
return new ResponseEntity&lt;&gt;(errorDTO, HttpStatus.CONFLICT);
}
}
}

You also need to define the constraint name in your Model entity/database, such as:

@Table(uniqueConstraints = {
@UniqueConstraint(name = UNIQUE_CONSTRAINT_VALUE, columnNames = {&quot;value&quot;})})
public static final String UNIQUE_CONSTRAINT_VALUE = &quot;unique_constraint_property_value&quot;;

And in the database definition as well of course.

huangapple
  • 本文由 发表于 2020年5月2日 15:13:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/61555717.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定