hibernar validación de clave única
-
26-09-2019 - |
Pregunta
Tengo un campo, por ejemplo, user_name
, que debe ser único en una tabla.
¿Cuál es la mejor manera de validar que el uso de la validación de Primavera / Hibernate?
Solución
Una de las posibles soluciones es crear restricción @UniqueKey
personalizado (y validador correspondiente); y mirar arriba los registros existentes en la base de datos, proporcionar una instancia de EntityManager
(o Session
Hibernate) 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;
}
....
}
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());
}
Otros consejos
Creo que no es aconsejable utilizar Hibernate Validator (JSR 303) para este propósito. O mejor no es el objetivo de Hibernate Validator.
El JSR 303 es acerca de la validación de frijol. Estos medios para comprobar si un campo se establece correcta. Pero lo que quiere es en un ámbito mucho más amplio que un solo grano. Es de alguna manera en un ámbito global (en relación con todos los beans de este tipo). - Creo que debería dejar que la base de datos de manejar este problema. Establecer una restricción única para la columna en su base de datos (por ejemplo, mediante anotaciones en el campo con @Column(unique=true)
) y la base de datos se asegurará de que el campo es único.
De todos modos, si realmente desea utilizar JSR303 para esto, de lo que necesita para crear su propia anotación y el propio validador. El Validador tiene que tener acceso a la base de datos y verificación si no existe otra entidad con el valor especificado. - Pero creo que habría algunos problemas para acceder a la base de datos del Validador en la sesión de la derecha
.Una posibilidad es realizar anotaciones en el campo como @NaturalId
Se puede usar el atributo @Column
que se puede configurar como unique
.
Este código se basa en la anterior implementarse usando EntityManager
.
En caso de necesidad de utilizar cualquiera de hibernación Session
.
Anotación de encargo usando 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.
En primer lugar, he implementado el contraint ú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)
) ;
Se ve que me las arreglo etiquetas XML que se definen por una etiqueta única y un campo de texto llamado "XPath".
De todos modos, el segundo paso es coger simplemente se levanta el error cuando el usuario intenta hacer una mala actualización. Una mala actualización es cuando se trata de sustituir la etiqueta actual mediante una etiqueta existente. Si deja la etiqueta intacta, no problemo. Por lo tanto, 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
}
}
}
Este conflicto puede con el patrón @Unique pero se puede utilizar este método sucia a la adición válida también.
Nota: todavía hay un problema:. Si otros errores de validación son atrapados antes de la excepción, no se mostrará el mensaje sobre la unicidad