Question

J'ai un champ, par exemple, user_name, qui doit être unique dans un tableau.

Quelle est la meilleure façon pour valider à l'aide de la validation Spring / Hibernate?

Était-ce utile?

La solution

L'une des solutions possibles est de créer la contrainte @UniqueKey personnalisée (et validateur correspondante); et Recherch les enregistrements existants dans la base de données, fournir une instance de EntityManager (ou 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;
    }

}

Utilisation

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

Autres conseils

Je pense qu'il est pas sage d'utiliser Hibernate Validator (JSR 303) à cet effet. Ou mieux c'est pas le but d'Hibernate Validator.

Le JSR 303 est sur la validation du haricot. Cela signifie pour vérifier si un champ est correct. Mais ce que vous voulez est dans une portée beaucoup plus large qu'un seul haricot. Il est en quelque sorte dans une portée globale (en ce qui concerne tous les haricots de ce type). - Je pense que vous devriez laisser la base de données gérer ce problème. Définir une contrainte unique à la colonne dans votre base de données (par exemple annoter le terrain avec @Column(unique=true)) et la base de données fera en sorte que le champ est unique.

Quoi qu'il en soit, si vous voulez vraiment utiliser JSR303 pour cela, que vous devez créer votre propre Annotation et posséder Validator. Le validateur doivent accéder à la base de données et vérifier s'il n'y a pas d'autre entité avec la valeur spécifiée. - Mais je crois qu'il y aurait des problèmes d'accès à la base de données de la Validator dans la bonne session

.

Il est possible d'annoter le terrain @NaturalId

Vous pouvez utiliser l'attribut @Column qui peut être défini comme unique.

Ce code est basé sur le précédent mis en œuvre à l'aide EntityManager. Au cas où quelqu'un doit utiliser Hibernate Session. Annotation personnalisée à l'aide 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;
    }
}

Utilisation

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

J'ai trouvé une sorte de solution délicate.

D'abord, j'ai mis l'unique ma contraint base de données 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)
) ;

Vous voyez que je gère balises XML qui sont définis par une étiquette unique et un champ de texte nommé « XPath ».

Quoi qu'il en soit, la deuxième étape consiste à attraper simplement l'erreur relevée lorsque l'utilisateur tente de faire une mauvaise mise à jour. Une mauvaise mise à jour est en essayant de remplacer l'étiquette actuelle par une étiquette existante. Si vous laissez l'étiquette intacte, no problemo. Ainsi, dans mon contrôleur:

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

Cela peut entrer en conflit avec le motif @Unique mais vous pouvez utiliser cette méthode sale pour valider l'ajout de trop.

Note: il y a encore un problème. Si d'autres erreurs de validation sont attrapés avant que l'exception, ne sera pas affiché le message sur unicidad

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top