Подтвердить что ты не робот

Hibernate уникальная проверка ключа

У меня есть поле, скажем, user_name, которое должно быть уникальным в таблице.

Каков наилучший способ проверки его с помощью валидации Spring/Hibernate?

4b9b3361

Ответ 1

Одним из возможных решений является создание пользовательского ограничения @UniqueKey (и соответствующего валидатора); и для поиска существующих записей в базе данных, укажите экземпляр EntityManager (или Hibernate Session) на 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;
    }

}

Использование

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

Ответ 2

Я думаю, что не разумно использовать Hibernate Validator (JSR 303) для этой цели. Или лучше это не цель Hibernate Validator.

JSR 303 имеет значение bean. Это означает, что поле задано правильно. Но то, что вы хотите, намного шире, чем один bean. Это как-то в глобальном масштабе (относительно всех Beans этого типа). - Я думаю, вы должны позволить базе данных справиться с этой проблемой. Установите уникальное ограничение для столбца в вашей базе данных (например, аннотируйте поле с помощью @Column(unique=true)), и база данных будет убедиться, что это поле уникально.

В любом случае, если вы действительно хотите использовать JSR303 для этого, вам нужно создать собственную аннотацию и собственный валидатор. Валидатор должен получить доступ к базе данных и проверить, нет ли другого объекта с указанным значением. - Но я считаю, что возникнут некоторые проблемы с доступом к базе данных из Validator в правильном сеансе.

Ответ 3

Одна из возможностей состоит в том, чтобы аннотировать поле как @NaturalId

Ответ 4

Вы можете использовать атрибут @Column, который можно установить как unique.

Ответ 5

Этот код основан на предыдущем, реализованном с использованием 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<? extends Payload>[] 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<UniqueKey, String> {

    @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

Ответ 6

Я нашел своеобразное сложное решение.

Во-первых, я внедрил уникальную возможность в базу данных 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)
) ;

Вы видите, что я управляю тегами XML, которые определены уникальной меткой и текстовым полем под названием "XPath".

В любом случае, второй шаг - просто уловить ошибку, возникшую, когда пользователь пытается выполнить плохую версию. Плохое обновление - это попытка заменить текущую метку существующей меткой. Если вы оставите ярлык нетронутым, не проблема. Итак, в моем контроллере:

    @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, но вы можете использовать этот грязный метод для правильного добавления.

Примечание: есть еще одна проблема: если другие ошибки проверки будут обнаружены до исключения, сообщение об однозначности не будет отображаться.