Ich habe ein Feld, sagen wir user_name
, die in einer Tabelle eindeutig sein sollte.Hibernate eindeutige Schlüssel Validierung
Was ist der beste Weg, um es mit Spring/Hibernate Validierung zu validieren?
Ich habe ein Feld, sagen wir user_name
, die in einer Tabelle eindeutig sein sollte.Hibernate eindeutige Schlüssel Validierung
Was ist der beste Weg, um es mit Spring/Hibernate Validierung zu validieren?
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());
}
Eine Möglichkeit ist, das Feld als @NaturalId
Sie das @Column
Attribut nutzen könnte, um mit Anmerkungen versehen, die als unique
eingestellt werden können.
Ich denke, das ist falsch. Sehen Sie meinen Kommentar auf Ralphs Antwort – Janning
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.
Danke, im Fall @Column (unique = true) Wie kann ich Fehler in Sicht zeigen – nidhin
@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
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
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.
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
Bitte beachten Sie, dass Sie eine Erklärung hinzufügen müssen, wie Ihr Code die beste Methode ist. –
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. –
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
Vielen Dank für Ihre Antwort – nidhin
Bitte fügen Sie dem Code Paketnamen hinzu. –
Wie kann ich den gleichen Code mit ** Sessionfactory ** anstelle von ** EntityManager ** verwenden? – FuSsA