validator validate starter example body bean java hibernate spring bean-validation hibernate-validator

java - validate - validación de clave única de hibernación



spring boot validator (6)

Tengo un campo, digamos, user_name , que debe ser único en una tabla.

¿Cuál es la mejor manera de validarlo usando la validación de Spring / Hibernate?


Creo que no es prudente utilizar el validador de hibernación (JSR 303) para este propósito. O mejor, no es el objetivo de Hibernate Validator.

El JSR 303 es sobre la validación del bean. Esto significa verificar si un campo está configurado correctamente. Pero lo que desea está en un alcance mucho más amplio que un solo bean. De alguna manera está en un ámbito global (con respecto a todos los Beans de este tipo). - Creo que deberías dejar que la base de datos maneje este problema. Establezca una restricción única para la columna en su base de datos (por ejemplo, anote el campo con @Column(unique=true) ) y la base de datos se asegurará de que el campo sea único.

De todos modos, si realmente desea usar JSR303 para esto, entonces necesita crear su propia anotación y su propio validador. El validador debe acceder a la base de datos y verificar si no hay otra entidad con el valor especificado. - Pero creo que habría algunos problemas para acceder a la base de datos desde el Validador en la sesión correcta.


Este código se basa en el anterior implementado utilizando EntityManager . En caso de que alguien necesite utilizar Hibernate Session . Anotación personalizada utilizando 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


He encontrado una especie de solución difícil.

Primero, he implementado el enlace único a mi base de datos 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) ) ;

Verá que administro las etiquetas XML que están definidas por una etiqueta única y un campo de texto llamado "XPath".

De todos modos, el segundo paso es simplemente detectar el error generado cuando el usuario intenta realizar una actualización incorrecta. Una mala actualización es cuando se intenta reemplazar la etiqueta actual por una etiqueta existente. Si dejas la etiqueta intacta, no hay problema. Entonces, en mi controlador:

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

Esto puede entrar en conflicto con el patrón @Unique pero también puede usar este método sucio para validar la adición.

Nota: todavía hay un problema: si se detectan otros errores de validación antes de la excepción, no se mostrará el mensaje acerca de la unicidad.


Podría usar el atributo @Column que se puede establecer como unique .


Una de las posibles soluciones es crear la restricción personalizada @UniqueKey (y el validador correspondiente); y para buscar los registros existentes en la base de datos, proporcione una instancia de EntityManager (o Hibernate Session ) a UniqueKeyValidator .

EntityManagerAwareValidator

public interface EntityManagerAwareValidator { void setEntityManager(EntityManager entityManager); }

RestricciónValidatorFactoryImpl

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

Llave unica

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

Prueba

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


Una posibilidad es anotar el campo como @NaturalId