Java 标识触发DataIntegrityViolationException的约束名称
我遇到了一个确定哪个约束触发DataIntegrityViolationException的问题。我有两个独特的限制:用户名和电子邮件,但我没有运气去弄清楚它 我已尝试获取根本原因异常,但收到此消息Java 标识触发DataIntegrityViolationException的约束名称,java,spring,hibernate,jpa,Java,Spring,Hibernate,Jpa,我遇到了一个确定哪个约束触发DataIntegrityViolationException的问题。我有两个独特的限制:用户名和电子邮件,但我没有运气去弄清楚它 我已尝试获取根本原因异常,但收到此消息 唯一索引或主键冲突:“UK_6DOTKOTT2KJSP8VW4D0M25FB7_索引_4 ON PUBLIC.USERS(EMAIL)值('copeland@yahoo.com', 21)"; SQL语句: 在用户(id、创建地址、更新地址、国家/地区、电子邮件、姓氏、姓名、密码、电话、性别、用户名
唯一索引或主键冲突:“UK_6DOTKOTT2KJSP8VW4D0M25FB7_索引_4 ON PUBLIC.USERS(EMAIL)值('copeland@yahoo.com', 21)"; SQL语句:
在用户(id、创建地址、更新地址、国家/地区、电子邮件、姓氏、姓名、密码、电话、性别、用户名)中插入值(空、、、、、、、、、、、、)[23505-193]
读取错误“我知道电子邮件”约束会触发验证,但我希望返回给用户类似的内容:
{键入:错误,消息:“电子邮件已存在”}
我在其他帖子中读过,人们在异常中寻找约束名称(例如,users\u unique\u username\u idx
),并向用户显示适当的消息。但我无法获取该类型的约束名称
也许我缺少一个配置。我正在使用:
springboot1.5.1.RELEASE、JPA、Hibernate和H2
我的应用程序.properties
@ControllerAdvice
public class ControllerValidationHandler {
private final Logger LOGGER = LoggerFactory.getLogger(ControllerValidationHandler.class);
@Autowired
private MessageSource msgSource;
private static Map<String, String> constraintCodeMap = new HashMap<String, String>() {
{
put("users_unique_username_idx", "exception.users.duplicate_username");
put("users_unique_email_idx", "exception.users.duplicate_email");
}
};
// This solution I see in another stackoverflow answer but not work
// for me. This is the closest solution to solve my problem that I found
@ResponseStatus(value = HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseBody
public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) {
String rootMsg = ValidationUtil.getRootCause(e).getMessage();
LOGGER.info("rootMessage" + rootMsg);
if (rootMsg != null) {
Optional<Map.Entry<String, String>> entry = constraintCodeMap.entrySet().stream()
.filter((it) -> rootMsg.contains(it.getKey()))
.findAny();
LOGGER.info("Has entries: " + entry.isPresent()); // false
if (entry.isPresent()) {
LOGGER.info("Value: " + entry.get().getValue());
e=new DataIntegrityViolationException(
msgSource.getMessage(entry.get().getValue(), null, LocaleContextHolder.getLocale()));
}
}
return new ErrorInfo(req, e);
}
@Service
@Qualifier("mysql")
class MysqlUserService implements UserService {
private UserRepository userRepository;
@Autowired
public MysqlUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public Page<User> findAll(Pageable pageable) {
return userRepository.findAll(pageable);
}
@Override
public User findOne(Long id) {
return userRepository.findOne(id);
}
@Override
public User store(User user) {
return userRepository.save(user);
}
@Override
public User update(User usr) {
User user = this.validateUser(usr);
return userRepository.save(user);
}
@Override
public void destroy(Long id) {
this.validateUser(id);
userRepository.delete(id);
}
private User validateUser(User usr) {
return validateUser(usr.getId());
}
/**
* Validate that an user exists
*
* @param id of the user
* @return an existing User
*/
private User validateUser(Long id) {
User user = userRepository.findOne(id);
if (user == null) {
throw new UserNotFoundException();
}
return user;
}
}
spring.jpa.generate ddl=true
用户类:
@Entity(name = "users")
public class User extends BaseEntity {
private static final Logger LOGGER = LoggerFactory.getLogger(User.class);
public enum Sex { MALE, FEMALE }
@Id
@GeneratedValue
private Long id;
@Column(name = "name", length = 100)
@NotNull(message = "error.name.notnull")
private String name;
@Column(name = "lastName", length = 100)
@NotNull(message = "error.lastName.notnull")
private String lastName;
@Column(name = "email", unique = true, length = 100)
@NotNull(message = "error.email.notnull")
private String email;
@Column(name = "username", unique = true, length = 100)
@NotNull(message = "error.username.notnull")
private String username;
@Column(name = "password", length = 100)
@NotNull(message = "error.password.notnull")
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
@Enumerated(EnumType.STRING)
private Sex sex;
@Column(name = "phone", length = 50)
private String phone;
@Column(name = "country", length = 100)
@NotNull(message = "error.country.notnull")
private String country;
public User() {}
// Getters and setters
}
ControllerValidationHandler.class
@ControllerAdvice
public class ControllerValidationHandler {
private final Logger LOGGER = LoggerFactory.getLogger(ControllerValidationHandler.class);
@Autowired
private MessageSource msgSource;
private static Map<String, String> constraintCodeMap = new HashMap<String, String>() {
{
put("users_unique_username_idx", "exception.users.duplicate_username");
put("users_unique_email_idx", "exception.users.duplicate_email");
}
};
// This solution I see in another stackoverflow answer but not work
// for me. This is the closest solution to solve my problem that I found
@ResponseStatus(value = HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseBody
public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) {
String rootMsg = ValidationUtil.getRootCause(e).getMessage();
LOGGER.info("rootMessage" + rootMsg);
if (rootMsg != null) {
Optional<Map.Entry<String, String>> entry = constraintCodeMap.entrySet().stream()
.filter((it) -> rootMsg.contains(it.getKey()))
.findAny();
LOGGER.info("Has entries: " + entry.isPresent()); // false
if (entry.isPresent()) {
LOGGER.info("Value: " + entry.get().getValue());
e=new DataIntegrityViolationException(
msgSource.getMessage(entry.get().getValue(), null, LocaleContextHolder.getLocale()));
}
}
return new ErrorInfo(req, e);
}
@Service
@Qualifier("mysql")
class MysqlUserService implements UserService {
private UserRepository userRepository;
@Autowired
public MysqlUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public Page<User> findAll(Pageable pageable) {
return userRepository.findAll(pageable);
}
@Override
public User findOne(Long id) {
return userRepository.findOne(id);
}
@Override
public User store(User user) {
return userRepository.save(user);
}
@Override
public User update(User usr) {
User user = this.validateUser(usr);
return userRepository.save(user);
}
@Override
public void destroy(Long id) {
this.validateUser(id);
userRepository.delete(id);
}
private User validateUser(User usr) {
return validateUser(usr.getId());
}
/**
* Validate that an user exists
*
* @param id of the user
* @return an existing User
*/
private User validateUser(Long id) {
User user = userRepository.findOne(id);
if (user == null) {
throw new UserNotFoundException();
}
return user;
}
}
更新#2
回购重现发行。为了查看异常,我在ValidationExceptionHandler.class
上注释了我的处理程序
在Readme.md上的json测试中发送两次json到
POST/users/
,而不是在@column
注释中指定唯一的列要求,您可以使用JPA提供的@表
注释上的名称实际定义这些约束,以进一步控制这些约束
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(name = "UC_email", columnNames = { "email" } ),
@UniqueConstraint(name = "UC_username", columnNames = " { "userName" } )
})
现在有两种处理异常的方法:
在控制器中
您可以选择将解析逻辑放在控制器中,只需捕获spring抛出的DataIntegrityException
,然后在那里解析它。类似于以下伪代码:
public ResponseBody myFancyControllerMethod(...) {
try {
final User user = userService.myFactoryServiceMethod(...);
}
catch ( DataIntegrityException e ) {
// handle exception parsing & setting the appropriate error here
}
}
public User myFancyServiceMethod(...) {
try {
// do your stuff here
return userRepository.save( user );
}
catch( ConstraintViolationException e ) {
if ( isExceptionUniqueConstraintFor( "UC_email" ) ) {
throw new EmailAddressAlreadyExistsException();
}
else if ( isExceptionUniqueConstraintFor( "UC_username" ) ) {
throw new UserNameAlreadyExistsException();
}
}
}
对我来说,这种方法的最终关键是我们将处理持久性问题的代码移到了两层,而不是紧靠持久性层之上的那一层。这意味着我们应该有多个控制器来处理这个场景,或者我们发现自己正在执行以下操作之一
- 引入一些抽象的基本控制器来放置逻辑
- 引入一些带有静态方法的助手类,以便重用
- 剪切粘贴代码-是的,这种情况比我们想象的要多
ConstraintViolationException
后抛出这些异常
在web控制器、rest控制器或调用服务的任何其他使用者中,如果需要,只需捕获适当的异常类并相应地进行分支。下面是一些服务伪代码:
public ResponseBody myFancyControllerMethod(...) {
try {
final User user = userService.myFactoryServiceMethod(...);
}
catch ( DataIntegrityException e ) {
// handle exception parsing & setting the appropriate error here
}
}
public User myFancyServiceMethod(...) {
try {
// do your stuff here
return userRepository.save( user );
}
catch( ConstraintViolationException e ) {
if ( isExceptionUniqueConstraintFor( "UC_email" ) ) {
throw new EmailAddressAlreadyExistsException();
}
else if ( isExceptionUniqueConstraintFor( "UC_username" ) ) {
throw new UserNameAlreadyExistsException();
}
}
}
您可以单独指定唯一的约束,但需要在实体级别指定,如
@Entity(name = "users")
@Table(name = "users", uniqueConstraints = {
@UniqueConstraint(name = "users_unique_username_idx", columnNames = "username"),
@UniqueConstraint(name = "users_unique_email_idx", columnNames = "email")
})
public class User extends BaseEntity { ... }
您可以单独指定唯一约束,但需要在实体级别指定,如
@Table(name=”“,uniqueConstraints={@UniqueConstraint(name=“users\u unique\u username\u idx”,columnNames=“username”),@UniqueConstraint(name=“users\u unique\u email\u idx”,columnNames=“email”)}
谢谢。这样做的诀窍,回答问题,标记为接受我有这个第一,但要100%确定电子邮件不存在,我将锁定表。我不记得在哪里读到过这篇文章,但他们建议捕获异常而不是查询dbI更新了我的答案,如果遇到任何问题,请随时告诉我。我更新了我的问题以添加我的服务层。感谢@CristianBeikov,我解决了我的第一个问题,那就是如何创建友好的约束名称,我得到的约束是UK\u 6DOTKOTT2KJSP8VW4D0M25FB7\u INDEX\u 4
。最后,我在根异常字符串上检查约束键以解决问题。感谢您的更新,我看到ConstraintViolationException
有一个getConstraintName()
方法。如何将DataIntegrityViolationException
转换为ConstraintViolationException
?也许我看不到它,因为它是一个jdbception
?没错,Spring数据可能在这一点上抛出DataIntegrityViolationException
。DataIntegrityViolationException#getRootCause()
是否返回ConstraintViolationException
?您可能需要检查它是否是该类型和类型转换,以获得您提到的方法。如果是这样,我可以更新我的答案。现在我阅读了我看到的整个异常跟踪ConstraintViolationException
,但根本原因是DataIntegrityViolationException
,这是我可以在@ControllerAdvice
上捕捉到的异常。我评论了我的处理程序,并用我的项目更新了我的问题,以重现异常。如有任何意见或建议,将不胜感激