英文:
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:
<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>
答案1
得分: 3
你正在混合使用不同的东西。
Bean验证
Bean验证
API 允许您验证您的bean,使用 javax.validation.constraints
包中的 @NotNull
、@Size
和其他所有注释。
您可以使用 Validator
来验证您的bean,并获得一个包含属性信息的 javax.validation.ConstraintViolationException
的 ConstraintViolation
。
JPA/Hibernate 支持 Bean验证
API。如果 persistence.xml
中的 <validation-mode>
元素设置为 AUTO
(或者未设置,因为 AUTO
是默认值),则 JPA 将在使用 Validator
保存实体之前验证它们,并在存在 Bean验证
实现的环境中抛出 javax.validation.ConstraintViolationException
,如果存在任何 ConstraintViolation
。如果将 <validation-mode>
设置为 CALLBACK
,则 JPA 将始终验证实体,并在环境中没有 Bean验证
实现时在部署期间引发错误。
SQL约束
JPA注释,如 @Column
,提供生成数据库DDL的信息。使用 @Column(nullable = false, length = 10)
将生成一个具有最大长度为10和 NOT NULL
SQL约束的数据库列。但这只是生成DDL,Hibernate不会在将语句发送到数据库之前进行检查。
如果提供了一个null
值,数据库将报告一个SQL错误,该错误将由JDBC驱动程序映射为 SQLException
。SQLException
中的错误消息对于每个DBMS都是不同的,但将包含约束名称。Hibernate将这些异常封装在 org.hibernate.exception.ConstraintViolationException
中,并从SQLException消息中提取约束名称。
结论
-
在您的
persistence.xml
上将<validation-mode>
设置为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 <validation-mode>
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 ConstraintViolation
s, only if an implementation of Bean Validation
is present on the environment. If you set <validation-mode>
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
<validation-mode>
toCALLBACK
on yourpersistence.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<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);
}
}
}
You also need to define the constraint name in your Model entity/database, such as:
@Table(uniqueConstraints = {
@UniqueConstraint(name = UNIQUE_CONSTRAINT_VALUE, columnNames = {"value"})})
public static final String UNIQUE_CONSTRAINT_VALUE = "unique_constraint_property_value";
And in the database definition as well of course.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论