hibernate唯一密钥验证

nid*_*hin 17 java spring hibernate hibernate-validator bean-validation

比方说,我有一个字段,user_name在表格中应该是唯一的.

使用Spring/Hibernate验证验证它的最佳方法是什么?

dir*_*ira 23

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

EntityManagerAwareValidator

public interface EntityManagerAwareValidator {  
     void setEntityManager(EntityManager entityManager); 
} 
Run Code Online (Sandbox Code Playgroud)

ConstraintValidatorFactoryImpl

public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory {

    private EntityManagerFactory entityManagerFactory;

    public ConstraintValidatorFactoryImpl(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

唯一键

@Constraint(validatedBy={UniqueKeyValidator.class})
@Target({ElementType.TYPE})
@Retention(RUNTIME)
public @interface UniqueKey {

    String[] columnNames();

    String message() default "{UniqueKey.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ ElementType.TYPE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        UniqueKey[] value();
    }
}
Run Code Online (Sandbox Code Playgroud)

UniqueKeyValidator

public class UniqueKeyValidator implements ConstraintValidator<UniqueKey, Serializable>, 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<Object> criteriaQuery = criteriaBuilder.createQuery();

        Root<?> root = criteriaQuery.from(entityClass);

        List<Predicate> predicates = new ArrayList<Predicate> (columnNames.length);

        try {
            for(int i=0; i<columnNames.length; i++) {
                String propertyName = columnNames[i];
                PropertyDescriptor desc = new PropertyDescriptor(propertyName, entityClass);
                Method readMethod = desc.getReadMethod();
                Object propertyValue = readMethod.invoke(target);
                Predicate predicate = criteriaBuilder.equal(root.get(propertyName), propertyValue);
                predicates.add(predicate);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        criteriaQuery.where(predicates.toArray(new Predicate[predicates.size()]));

        TypedQuery<Object> typedQuery = entityManager.createQuery(criteriaQuery);

        List<Object> resultSet = typedQuery.getResultList(); 

        return resultSet.size() == 0;
    }

}
Run Code Online (Sandbox Code Playgroud)

用法

@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;
    }
        ....
}
Run Code Online (Sandbox Code Playgroud)

测试

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<ConstraintViolation<User>> 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());
}
Run Code Online (Sandbox Code Playgroud)


Ral*_*lph 7

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

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

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

  • 当然,数据库需要一个唯一约束来处理并发插入.但我不认为你应该让数据库单独处理它.之前验证唯一性使得向用户显示错误消息变得更加容易.因此,在我看来,事先进行验证并测试唯一性是一种非常有效的方法.通常,当用户在其Web浏览器中保留用户名字段时,您希望"用户名不可用".所以做一个ajax请求并试图用jsr-303验证是要走的路.这就是我拒绝投票的原因(对不起) (4认同)
  • Ajax只是一个例子.如果您有业务规则,则应对其进行验证.想象一下没有ajax的相同例子.您提供用户名和电子邮件地址.电子邮件地址无效,因此您会收到错误页面.但是,由于您没有尝试更新数据库,因此无法根据其选择的用户名向用户显示相应的消息.用户更正电子邮件地址,提交并获取下一个错误,谈论用户名的唯一性. (3认同)

Rag*_*ram 5

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