2011-01-06 93 views
16

Ho un campo, ad esempio, user_name, che dovrebbe essere univoco in una tabella.hibernate convalida chiave univoca

Qual è il modo migliore per convalidarlo utilizzando la convalida Spring/Hibernate?

risposta

22

Una delle possibili soluzioni è creare un vincolo personalizzato @UniqueKey (e un validatore corrispondente); e per cercare i record esistenti nel database, fornire un'istanza di EntityManager (o Hibernate Session) a 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; 
    } 

} 

Uso

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

prova

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

Grazie per la risposta – nidhin

+0

Si prega di aggiungere i nomi dei pacchetti al codice. –

+0

come posso usare lo stesso codice con ** Sessionfactory ** invece di ** EntityManager **? – FuSsA

4

Una possibilità è quella di annotare il campo come @NaturalId

+0

Penso che questo è sbagliato. guarda il mio commento alla risposta di Ralph. – Janning

+0

Uno su di me, perché hai affermato chiaramente che questa è una "possibile soluzione". – Ralph

2

Si potrebbe utilizzare l'attributo @Column che può essere impostato come unique.

+0

Penso che questo sia sbagliato. vedere il mio commento alla risposta di Ralph – Janning

6

Penso che non sia saggio utilizzare Hibernate Validator (JSR 303) per questo scopo. O meglio non è l'obiettivo di Hibernate Validator.

Il JSR 303 riguarda la convalida del bean. Questo significa verificare se un campo è impostato correttamente. Ma quello che vuoi è in un ambito molto più ampio di un singolo fagiolo. È in qualche modo in un ambito globale (per quanto riguarda tutti i fagioli di questo tipo). - Penso che dovresti lasciare che il database gestisca questo problema. Impostare un vincolo univoco per la colonna nel database (ad esempio annotare il campo con @Column(unique=true)) e il database si assicurerà che il campo sia univoco.

In ogni caso, se si desidera veramente utilizzare JSR303 per questo, è necessario creare la propria annotazione e il proprio Validatore. Il validatore deve accedere al database e verificare se non ci sono altre entità con il valore specificato. - Ma credo che ci sarebbero alcuni problemi per accedere al database dal Validator nella sessione giusta.

+0

Grazie, nel caso @Column (unique = true) come posso mostrare l'errore in vista – nidhin

+0

@Column (unique = true) creerà un vincolo di base di dati nella colonna corrispondente. - Questo significa che tutto ciò che ottieni è un'eccezione se provi a memorizzare l'entità con il valore non univoco. - Quindi non c'è (facile) modo di combinarlo con la convalida del bean. – Ralph

+3

Ovviamente, il database richiede un vincolo univoco per gestire inserimenti contemporanei. Ma non penso che dovresti lasciare che il database gestisca da solo. Convalidare l'unicità prima rende molto più facile mostrare all'utente i messaggi di errore. Quindi secondo me è un approccio perfettamente valido per fare la convalida in anticipo e testare l'unicità. Di solito vuoi avere "username non disponibile" non appena l'utente lascia il campo del nome utente nel suo browser web. Quindi, fare una richiesta Ajax e provare a convalidare con jsr-303 è la strada da percorrere. Ecco perché ho votato la tua risposta (scusami per questo) – Janning

1

ho trovato una specie di soluzione difficile.

In primo luogo, ho implementato il contraint unica al mio database 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) 
) ; 

si vede che gestisco Tag XML che sono definiti da un'etichetta unica e un campo di testo denominato "XPath".

In ogni caso, il secondo passaggio consiste semplicemente nel rilevare l'errore generato quando l'utente tenta di eseguire un aggiornamento errato. Un cattivo aggiornamento si verifica quando si tenta di sostituire l'etichetta corrente con un'etichetta esistente. Se lasci l'etichetta intoccata, non c'è dubbio. Quindi, a mio regolatore:

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

Questo può entrare in conflitto con il modello @Unique ma è possibile utilizzare questo metodo sporco per valida l'aggiunta di troppo.

Nota: c'è ancora un problema: se vengono rilevati altri errori di convalida prima dell'eccezione, il messaggio sull'unicità non verrà visualizzato.

2

Questo codice è basato sul precedente implementato utilizzando EntityManager. Nel caso in cui qualcuno debba utilizzare Hibernate Session. Annotazione personalizzata utilizzando 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; 
    } 
} 

Uso

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

Si prega di aggiungere una spiegazione su come il codice è il metodo migliore. –

+0

Il codice è basato su quello precedente usando ** EnintyManager **. Una persona ha richiesto la sessione di Hibernate ** **. Scusa per non averlo menzionato. E grazie. –

+0

Si noti che questa implementazione non supporta il test del validatore in un'impostazione di test dell'unità. Nelle impostazioni del test di unità i validatori non vengono creati con il meccanismo di creazione del bean Spring, quindi 'SessionFactory' deve essere iniettato manualmente nel validatore, possibilmente usando un' ConstraintValidatorFactory' personalizzato come risposta accettata. – infiniteRefactor

Problemi correlati