LTr*_*oya 5 java spring hibernate jpa
我遇到了确定哪个约束触发 DataIntegrityViolationException 的问题。我有两个独特的限制:用户名和电子邮件,但我没有运气试图弄清楚。
我尝试获取根本原因异常,但收到此消息
Unique index or primary key violation: "UK_6DOTKOTT2KJSP8VW4D0M25FB7_INDEX_4 ON PUBLIC.USERS(EMAIL) VALUES ('copeland@yahoo.com', 21)"; SQL statement:
insert into users (id, created_at, updated_at, country, email, last_name, name, password, phone, sex, username) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23505-193]
阅读错误我知道电子邮件约束会触发验证,但我想返回给用户,例如:
{type: ERROR, message: "The email already exist"}
我在其他帖子中读过,人们处理它,在异常中寻找约束名称(例如,users_unique_username_idx)并向用户显示正确的消息。但我无法获得那种类型的约束名称
也许我缺少一个配置。我在用:
Spring Boot 1.5.1.RELEASE, JPA, Hibernate and H2
我的应用程序属性
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
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
此时的回应是:
{"timestamp":1488063801557,"status":500,"error":"Internal Server Error","exception":"org.springframework.dao.DataIntegrityViolationException","message":"could not execute statement; SQL [n/a]; constraint [\"UK_6DOTKOTT2KJSP8VW4D0M25FB7_INDEX_4 ON PUBLIC.USERS(EMAIL) VALUES ('copeland@yahoo.com', 21)\"; SQL statement:\ninsert into users (id, created_at, updated_at, country, email, last_name, name, password, phone, sex, username) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23505-193]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement","path":"/users"}
更新
这是我的服务层,处理我的持久性操作
MysqlService.class
@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;
}
}
Run Code Online (Sandbox Code Playgroud)
更新#2
存储库来重现问题https://github.com/LTrya/boot-users。我评论了我的处理程序ValidationExceptionHandler.class以查看异常。
在 Json 中发送两次 json以在 Readme.md 上进行测试POST /users/
您想要做的不是在注释上指定唯一的列要求@Column,而是可以在 JPA 提供的注释上实际定义那些名称,@Table以进一步控制这些约束。
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(name = "UC_email", columnNames = { "email" } ),
@UniqueConstraint(name = "UC_username", columnNames = " { "userName" } )
})
Run Code Online (Sandbox Code Playgroud)
现在有两种处理异常的方法:
您可以选择将解析逻辑放置在控制器中,并简单地捕获DataIntegrityExceptionspring 抛出的异常并在那里解析它。类似下面的伪代码:
public ResponseBody myFancyControllerMethod(...) {
try {
final User user = userService.myFactoryServiceMethod(...);
}
catch ( DataIntegrityException e ) {
// handle exception parsing & setting the appropriate error here
}
}
Run Code Online (Sandbox Code Playgroud)
对我来说,这种方法的最终症结在于我们将处理持久性问题的代码移至两层,而不是直接位于持久层之上的层。这意味着如果我们有多个控制器需要处理这种情况,我们要么发现自己正在执行以下操作之一
当您需要与其他可能不会实际返回某种类型的 html 视图的使用者类型共享该服务时,将代码放置在表示层中也会带来问题。
这就是为什么我建议将逻辑再降低一级。
这是一种更简洁的方法,因为我们将约束处理的验证推送到持久层之上的层,这意味着最终是我们处理持久性失败的地方。不仅如此,我们的代码实际上记录了失败条件,我们可以根据上下文选择忽略或处理它们。
这里需要注意的是,我建议您创建从服务层代码中抛出的特定异常类,以便识别唯一的约束失败,并在解析ConstraintViolationExceptionHibernate 后抛出这些异常。
在您的 Web 控制器、休息控制器或调用您的服务的任何其他消费者中,您只需捕获适当的异常类(如果需要)并进行相应的分支。这是一些服务伪代码:
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
您可以单独指定唯一约束,但您需要在实体级别上指定唯一约束,例如
@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 { ... }
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4938 次 |
| 最近记录: |