hibernate唯一密钥validation

我有一个字段,比如user_name ,它在表中应该是唯一的。

使用Spring / Hibernatevalidationvalidation它的最佳方法是什么?

一种可能的解决方案是创建自定义@UniqueKey约束(和相应的validation器); 并查找数据库中的现有记录,向UniqueKeyValidator提供EntityManager (或Hibernate Session )的实例。

EntityManagerAwareValidator

 public interface EntityManagerAwareValidator { void setEntityManager(EntityManager entityManager); } 

ConstraintValidatorFactoryImpl

 public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory { private EntityManagerFactory entityManagerFactory; public ConstraintValidatorFactoryImpl(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } @Override public > T getInstance(Class key) { T instance = null; try { instance = key.newInstance(); } catch (Exception e) { // could not instantiate class e.printStackTrace(); } if(EntityManagerAwareValidator.class.isAssignableFrom(key)) { EntityManagerAwareValidator validator = (EntityManagerAwareValidator) instance; validator.setEntityManager(entityManagerFactory.createEntityManager()); } return instance; } } 

唯一键

 @Constraint(validatedBy={UniqueKeyValidator.class}) @Target({ElementType.TYPE}) @Retention(RUNTIME) public @interface UniqueKey { String[] columnNames(); String message() default "{UniqueKey.message}"; Class[] groups() default {}; Class[] payload() default {}; @Target({ ElementType.TYPE }) @Retention(RUNTIME) @Documented @interface List { UniqueKey[] value(); } } 

UniqueKeyValidator

 public class UniqueKeyValidator implements ConstraintValidator, EntityManagerAwareValidator { private EntityManager entityManager; @Override public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } private String[] columnNames; @Override public void initialize(UniqueKey constraintAnnotation) { this.columnNames = constraintAnnotation.columnNames(); } @Override public boolean isValid(Serializable target, ConstraintValidatorContext context) { Class entityClass = target.getClass(); CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); Root root = criteriaQuery.from(entityClass); List predicates = new ArrayList (columnNames.length); try { for(int i=0; i typedQuery = entityManager.createQuery(criteriaQuery); List resultSet = typedQuery.getResultList(); return resultSet.size() == 0; } } 

用法

 @UniqueKey(columnNames={"userName"}) // @UniqueKey(columnNames={"userName", "emailId"}) // composite unique key //@UniqueKey.List(value = {@UniqueKey(columnNames = { "userName" }), @UniqueKey(columnNames = { "emailId" })}) // more than one unique keys public class User implements Serializable { private String userName; private String password; private String emailId; protected User() { super(); } public User(String userName) { this.userName = userName; } .... } 

测试

 public void uniqueKey() { EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("default"); ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); ValidatorContext validatorContext = validatorFactory.usingContext(); validatorContext.constraintValidatorFactory(new ConstraintValidatorFactoryImpl(entityManagerFactory)); Validator validator = validatorContext.getValidator(); EntityManager em = entityManagerFactory.createEntityManager(); User se = new User("abc", poizon); Set> violations = validator.validate(se); System.out.println("Size:- " + violations.size()); em.getTransaction().begin(); em.persist(se); em.getTransaction().commit(); User se1 = new User("abc"); violations = validator.validate(se1); System.out.println("Size:- " + violations.size()); } 

我认为将Hibernate Validator(JSR 303)用于此目的并不明智。 或者更好的是它不是Hibernate Validator的目标。

JSR 303是关于beanvalidation的。 这意味着要检查字段是否设置正确。 但是你想要的是比单个bean更广泛的范围。 它在某种程度上处于全球范围内(关于这种类型的所有豆类)。 – 我认为你应该让数据库处理这个问题。 为数据库中的列设置唯一约束(例如,通过使用@Column(unique=true)注释该字段),数据库将确保该字段是唯一的。

无论如何,如果你真的想使用JSR303,那么你需要创建自己的Annotation和自己的Validator。 validation器必须访问数据库并检查是否没有具有指定值的其他实体。 – 但我相信在正确的会话中从Validator访问数据库会有一些问题。

一种可能性是将字段注释为@NaturalId

您可以使用可以设置为unique@Column属性。

此代码基于使用EntityManager实现的前一个代码。 如果有人需要使用Hibernate Session 。 使用Hibernate Session自定义注释。
@ UniqueKey.java

 import java.lang.annotation.*; import javax.validation.Constraint; import javax.validation.Payload; @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UniqueKeyValidator.class) @Documented public @interface UniqueKey { String columnName(); Class className(); String message() default "{UniqueKey.message}"; Class[] groups() default {}; Class[] payload() default {}; } 

UnqieKeyValidator.java

 import ch.qos.logback.classic.gaffer.PropertyUtil; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; @Transactional @Repository public class UniqueKeyValidator implements ConstraintValidator { @Autowired private SessionFactory sessionFactory; public Session getSession() { return sessionFactory.getCurrentSession(); } private String columnName; private Class entityClass; @Override public void initialize(UniqueKey constraintAnnotation) { this.columnNames = constraintAnnotation.columnNames(); this.entityClass = constraintAnnotation.className(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { Class entityClass = this.entityClass; System.out.println("class: " + entityClass.toString()); Criteria criteria = getSession().createCriteria(entityClass); try { criteria.add(Restrictions.eq(this.columnName, value)); } catch (Exception e) { e.printStackTrace(); } return criteria.list().size()==0; } } 

用法

 @UniqueKey(columnNames="userName", className = UserEntity.class) // @UniqueKey(columnNames="userName") // unique key 

我发现了一种棘手的解决方案。

首先,我已经实现了MySql数据库的唯一约束:

 CREATE TABLE XMLTAG ( ID INTEGER NOT NULL AUTO_INCREMENT, LABEL VARCHAR(64) NOT NULL, XPATH VARCHAR(128), PRIMARY KEY (ID), UNIQUE UQ_XMLTAG_LABEL(LABEL) ) ; 

您看到我管理由唯一标签和名为“XPath”的文本字段定义的XML标记。

无论如何,第二步是简单地捕获用户尝试进行错误更新时引发的错误。 尝试使用现有标签替换当前标签时,更新错误。 如果你保持标签不受影响,没有问题。 所以,在我的控制器中:

  @RequestMapping(value = "/updatetag", method = RequestMethod.POST) public String updateTag( @ModelAttribute("tag") Tag tag, @Valid Tag validTag, BindingResult result, ModelMap map) { if(result.hasErrors()) { // you don't care : validation of other return "editTag"; // constraints like @NotEmpty } else { try { tagService.updateTag(tag); // try to update return "redirect:/tags"; // <- if it works } catch (DataIntegrityViolationException ex) { // if it doesn't work result.rejectValue("label", "Unique.tag.label"); // pass an error message to the view return "editTag"; // same treatment as other validation errors } } } 

这可能与@Unique模式冲突,但您也可以使用此脏方法来validation添加。

注意:仍然存在一个问题:如果在exception之前捕获了其他validation错误,则不会显示有关unicity的消息。