休眠唯一密钥验证

问题描述 投票:0回答:7

我有一个字段,比如说,

user_name
,它在表格中应该是唯一的。

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

java hibernate spring bean-validation hibernate-validator
7个回答
26
投票

一种可能的解决方案是创建自定义

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

唯一键

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

测试

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

8
投票

我认为为此目的使用 Hibernate Validator (JSR 303) 是不明智的。 或者更好的是,这不是 Hibernate Validator 的目标。

JSR 303 是关于 bean 验证的。这意味着检查字段设置是否正确。但是你想要的是比单个 bean 更广泛的范围。它以某种方式处于全局范围内(关于这种类型的所有 Bean)。 -- 我觉得你应该让数据库来处理这个问题。为数据库中的列设置唯一约束(例如用

@Column(unique=true)
注释字段),数据库将确保该字段是唯一的。

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


5
投票

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


3
投票

您可以使用

@Column
属性,它可以设置为
unique
.


3
投票

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

首先,我对我的 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
        }
        

        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 模式冲突,但您也可以使用这种肮脏的方法来验证添加。

注意:还有一个问题:如果在异常之前捕获了其他验证错误,则不会显示有关unicity的消息。


2
投票

这段代码是基于之前使用

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

0
投票

从我的角度来看,这里提供的解决方案缺少非常重要的案例,即更新。我们在向我们的 JPA API 请求 persist 或 megre 时必须考虑主键,因此您 MUST 从唯一性检查中排除当前实体(通过使用主键)。

下面的演示使用了Spring Framework。

注释:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = UniqueValidator.class)
public @interface Unique {
    String[] fields();
    String primaryKey();

    String message() default "Email address must be unique!";

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

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

    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        Unique[] value();
    }
}

注解验证器实现:

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
public class UniqueValidator implements ConstraintValidator<Unique, Serializable> {
    @PersistenceContext
    private EntityManager entityManager;

    private String[] fields;
    private String primaryKey;

    @Override
    public void initialize(Unique constraintAnnotation) {
        this.fields = constraintAnnotation.fields();
        this.primaryKey = constraintAnnotation.primaryKey();
    }

    @Override
    public boolean isValid(Serializable target, ConstraintValidatorContext context) {
        log.info("start validation...");
        if(entityManager != null) {
            Class entityClass = target.getClass();
            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<?> criteriaQuery = criteriaBuilder.createQuery(entityClass);
            Root<?> root = criteriaQuery.from(entityClass);
            List<Predicate> predicates = new ArrayList(fields.length + 1);

            try {
                PropertyDescriptor desc = new PropertyDescriptor(primaryKey, entityClass);
                Method readMethod = desc.getReadMethod();
                Object propertyValue = readMethod.invoke(target);
                Predicate predicate = criteriaBuilder.notEqual(root.get(primaryKey), propertyValue);
                predicates.add(predicate);

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

            criteriaQuery.where(predicates.toArray(new Predicate[predicates.size()]));
            Query typedQuery = entityManager.createQuery(criteriaQuery);
            List<Object> resultSet = typedQuery.getResultList();
            log.info("found {}", resultSet);
            return resultSet.size() == 0;
        }
        return true;
    }
}

JPA实体:

@Unique(fields ={"email"}, primaryKey = "id")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer id;

    @Column(length = 128, nullable = false, unique = true)
    private String email;
}
© www.soinside.com 2019 - 2024. All rights reserved.