2011-01-06 39 views

Antwort

22

Eine der möglichen Lösungen ist, individuelle @UniqueKey Constraint zu schaffen (und die entsprechenden validator); und um die vorhandenen Datensätze in der Datenbank nachzuschlagen, stellen Sie EntityManager (oder Hibernate Session) UniqueKeyValidator bereit.

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

} 

Nutzungs

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

Vielen Dank für Ihre Antwort – nidhin

+0

Bitte fügen Sie dem Code Paketnamen hinzu. –

+0

Wie kann ich den gleichen Code mit ** Sessionfactory ** anstelle von ** EntityManager ** verwenden? – FuSsA

4

Eine Möglichkeit ist, das Feld als @NaturalId

+0

Ich denke, das ist falsch. Sehen Sie meinen Kommentar in Ralphs Antwort. – Janning

+0

Eine Verbesserung von mir, weil Sie klar gesagt haben, dass dies "eine mögliche" Lösung ist. – Ralph

2

Sie das @Column Attribut nutzen könnte, um mit Anmerkungen versehen, die als unique eingestellt werden können.

+0

Ich denke, das ist falsch. Sehen Sie meinen Kommentar auf Ralphs Antwort – Janning

6

Ich denke, es ist nicht klug, Hibernate Validator (JSR 303) für diesen Zweck zu verwenden. Oder besser, es ist nicht das Ziel von Hibernate Validator.

Der JSR 303 ist eine Bean-Validierung. Dies bedeutet zu prüfen, ob ein Feld korrekt eingestellt ist. Aber was Sie wollen, ist in einem viel größeren Umfang als eine einzelne Bohne. Es ist irgendwie in einem globalen Rahmen (in Bezug auf alle Beans dieser Art). - Ich denke, Sie sollten die Datenbank dieses Problem behandeln lassen. Legen Sie eine eindeutige Einschränkung für die Spalte in Ihrer Datenbank fest (z. B. indem Sie das Feld mit @Column(unique=true) annotieren), und die Datenbank stellt sicher, dass das Feld eindeutig ist.

Wie auch immer, wenn Sie wirklich JSR303 dafür verwenden möchten, müssen Sie Ihre eigene Annotation erstellen und einen eigenen Validator besitzen. Der Validator muss auf die Datenbank zugreifen und prüfen, ob keine andere Entität mit dem angegebenen Wert vorhanden ist. - Aber ich glaube, dass es einige Probleme geben würde, auf die Datenbank vom Validator in der richtigen Sitzung zuzugreifen.

+0

Danke, im Fall @Column (unique = true) Wie kann ich Fehler in Sicht zeigen – nidhin

+0

@Column (unique = true) wird eine Datenbank-Integritätsbedingung zu der entsprechenden Spalte erstellen. - Dies bedeutet, dass Sie nur eine Ausnahme erhalten, wenn Sie versuchen, die Entität mit dem nicht eindeutigen Wert zu speichern. - Es gibt also keine (einfache) Möglichkeit, sie mit der Bean-Validierung zu kombinieren. – Ralph

+3

Natürlich benötigt die Datenbank eine eindeutige Einschränkung für die gleichzeitige Verarbeitung von Einfügungen. Aber ich denke nicht, dass Sie die Datenbank alleine behandeln lassen sollten. Durch die Überprüfung der Eindeutigkeit wird es wesentlich einfacher, Fehlernachrichten für den Benutzer anzuzeigen. Meiner Meinung nach ist es also ein absolut gültiger Ansatz, die Validierung vorher durchzuführen und auf Einzigartigkeit zu prüfen. Normalerweise möchten Sie "Benutzername nicht verfügbar" haben, sobald der Benutzer das Feld "Benutzername" in seinem Webbrowser verlassen hat. Also eine Ajax-Anfrage machen und versuchen, mit jsr-303 zu validieren ist der Weg zu gehen. Deshalb habe ich deine Antwort abgelehnt (sorry dafür) – Janning

1

ich ein bisschen eine schwierige Lösung gefunden habe.

Zuerst habe ich die einzigartige contraint meiner MySQL-Datenbank implementiert:

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

Sie sehen, dass ich XML Tags und verwalten, die durch eine eindeutige Beschriftung und ein Textfeld mit dem Namen „XPath“ definiert ist.

Wie auch immer, der zweite Schritt besteht darin, einfach den Fehler abzufangen, der auftritt, wenn der Benutzer versucht, ein fehlerhaftes Update durchzuführen. Ein fehlerhaftes Update ist der Versuch, das aktuelle Etikett durch ein vorhandenes Etikett zu ersetzen. Wenn Sie das Etikett unberührt lassen, kein Problem. Also, in meinem Controller:

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

Dies kann mit dem @Unique Muster in Konflikt geraten, aber sie diese schmutzige Methode gültig auch das Hinzufügen verwenden können.

Hinweis: Es gibt immer noch ein Problem: Wenn andere Validierungsfehler vor der Ausnahme abgefangen werden, wird die Meldung über Unicity nicht angezeigt.

2

Dieser Code basiert auf dem vorherigen, der mit EntityManager implementiert wurde. Falls jemand Hibernate Session verwenden muss. Benutzerdefinierte Annotation mit 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; 
    } 
} 

Nutzungs

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

Bitte beachten Sie, dass Sie eine Erklärung hinzufügen müssen, wie Ihr Code die beste Methode ist. –

+0

Der Code basiert auf dem vorherigen Code ** EnintyManager **. Eine Person fragte nach der Hibernate ** Session **. Entschuldige, dass ich nicht erwähnt habe. Und danke dir. –

+0

Beachten Sie, dass diese Implementierung das Testen des Validators in einer Komponententesteinstellung nicht unterstützt. In Unit-Test-Einstellungen werden Validatoren nicht mit dem Spring-Bean-Erzeugungsmechanismus erzeugt, daher sollte 'SessionFactory' manuell in den Validator eingefügt werden, möglicherweise unter Verwendung einer benutzerdefinierten' ConstraintValidatorFactory' als akzeptierte Antwort. – infiniteRefactor

Verwandte Themen