Search code examples
javahibernatespringbean-validationhibernate-validator

hibernate unique key validation


I have a field, say, user_name, that should be unique in a table.

What is the best way for validating it using Spring/Hibernate validation?


Solution

  • One of the possible solutions is to create custom @UniqueKey constraint (and corresponding validator); and to look-up the existing records in database, provide an instance of EntityManager (or Hibernate Session)to UniqueKeyValidator.

    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 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;
        }
    }
    

    UniqueKey

    @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();
        }
    }
    

    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;
        }
    
    }
    

    Usage

    @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;
        }
            ....
    }
    

    Test

    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());
    }