Eindeutiger Schlüssel Validierung überwintern
-
26-09-2019 - |
Frage
Ich habe ein Feld, sagen wir, user_name
, die in einer Tabelle eindeutig sein sollte.
Was ist der beste Weg, um es mit Spring / Hibernate-Validierung für die Validierung?
Lösung
Eine der möglichen Lösungen ist individuelle @UniqueKey
Abhängigkeit zu erstellen (und entsprechende Validierer); und die vorhandenen Datensätze in der Datenbank Nachschau bietet eine Instanz von EntityManager
(oder Hibernate Session
) zu 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;
}
}
Verwendung
@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());
}
Andere Tipps
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 über Bean-Validierung. Dieses Mittel 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 umgehen dieses Problem lassen. Legen Sie eine eindeutige Einschränkung auf die Spalte in Ihrer Datenbank (zB durch annotate das Feld mit @Column(unique=true)
) und die Datenbank wird sicherstellen, dass das Feld eindeutig ist.
Wie auch immer, wenn Sie wirklich für diese JSR303 verwenden möchten, als Sie benötigen, um Ihre eigene Annotation und eigenen Validator zu erstellen. Der Validator hat die Datenbank und die Prüfung zugreifen, wenn es keine andere Einheit mit dem angegebenen Wert ist. - Aber ich glaube, es würde einige Probleme die Datenbank aus dem Validator in der rechten Sitzung zugreifen
.Eine Möglichkeit ist, das Feld als mit Anmerkungen versehen @NaturalId
Sie könnten das @Column
Attribut verwenden, die als unique
eingestellt werden kann.
Dieser Code basiert auf dem vorherigen implementiert EntityManager
verwenden.
Falls jemand Notwendigkeit Hibernate Session
zu verwenden.
Individuelle 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;
}
}
Verwendung
@UniqueKey(columnNames="userName", className = UserEntity.class)
// @UniqueKey(columnNames="userName") // unique key
Ich habe ein bisschen eine schwierige Lösung gefunden.
Als erstes 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, ist der zweite Schritt, um einfach die Fehler abfangen ausgelöst, wenn der Benutzer eine schlechte Update zu tun versucht. Eine schlechte Update ist bei dem Versuch, das aktuelle Etikett durch ein vorhandenes Label zu ersetzen. Wenn Sie das Label unberührt, kein Problemo verlassen. 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 Konflikt mit dem @Unique Muster aber Sie können diese schmutzige Methode gültig verwenden, um die Zugabe zu.
Hinweis: es gibt noch ein Problem:. Wenn andere Validierungsfehler vor der Ausnahme abgefangen werden, wird die Nachricht über unicity nicht angezeigt werden