====== Columnas únicas ====== En lecciones anteriores hemos tratado el tema de las claves primarias pero aún no habíamos explicado cómo validar que una columna es única. El [[http://jcp.org/en/jsr/detail?id=303|JSR 303: Bean Validation]] no define ninguna anotación para validar que una columna es única. El motivo de ello es porque validar que una columna es única lo suele hacer la base de datos mediante una clave única en la tabla. Para validar que una columna es única mediante el fichero de mapeo de Hibernate ''.hbm.xml'' simplemente deberemos añadir el atributo ''unique="true"'' a la columna que queramos hacer única. En la línea 8 podemos ver el atributo ''unique'' con el valor ''true'' que define que la propiedad ''login'' es única. Si queremos hacer ésto mismo usando notaciones es tan sencillo como añadir el atributo ''unique=true'' a la anotación ''@Column''. @Column(name="login",unique=true) private String login; Desgraciadamente ésto no es suficiente para que nuestra columna sea única. También deberemos hacer que la base de datos cree el índice con la columna única. Afortunadamente Hibernate posee una herramienta que nos genera todo el esquema de la base de datos como un Script de SQL para nuestra base de datos. Si ejecutamos el siguiente código Java se generará en el raíz de nuestro proyecto el fichero ''script.sql'' con la creación de las tablas y su índices. new org.hibernate.tool.hbm2ddl.SchemaExport(configuration).setOutputFile("script.sql").setDelimiter(";").create(true, false); Es esta línea de Java la que crea el Script SQL con la columna única. Siendo entonces la base de datos la encargada de comprobar la unicidad de la columna y **no** siendo Hibernate el encargado de hacerlo. Por supuesto posteriormente será necesario ejecutar el script manualmente contra la base de datos usando phpMyAdmin o cualquier otra herramienta. También podemos hacer que Hibernate lance automáticamente el script contra la base de datos poniendo el segundo parámetro del método ''create'' al valor ''true''. El problema de ésto es que el script que genera Hibernate **borra las tablas con un drop** y las vuelve a crear con lo que se pueden perder todos los datos. Así que es mejor revisar el script y lanzarlo manualmente. ===== org.hibernate.exception.ConstraintViolationException ===== El siguiente paso con las columnas únicas es tratar con la excepción que se lanzará cuando se intente insertar un valor que ya existe en la columna. La excepción que se lanza es org.hibernate.exception.ConstraintViolationException. Desgraciadamente esta excepción no hereda de la excepción javax.validation.ConstraintViolationException con lo que será necesario tratar las 2 excepciones. El código ahora para persistir una clase quedará así: Usuario usuario = new Usuario("lramirez","Luis","Ramirez","Cano","1234567","1234567"); try { session.beginTransaction(); session.save(usuario); session.getTransaction().commit(); } catch (javax.validation.ConstraintViolationException cve) { session.getTransaction().rollback(); System.out.println("No se ha podido insertar el profesor debido a los siguientes errores:"); for (ConstraintViolation constraintViolation : cve.getConstraintViolations()) { System.out.println("En el campo '" + constraintViolation.getPropertyPath() + "':" + constraintViolation.getMessage()); } } catch (org.hibernate.exception.ConstraintViolationException cve) { session.getTransaction().rollback(); System.out.println("No se ha podido insertar el profesor debido al siguiente error:"); System.out.println("El valor ya existe."+cve.getLocalizedMessage()); } Vemos cómo se han añadido las líneas de las 15 a la 19 para tratar la nueva excepción org.hibernate.exception.ConstraintViolationException. Debido a que las 2 excepciones tienen el mismo nombre de clase ,''ConstraintViolationException'', aunque en paquetes distintos, es necesario poner la FQCN de cada una de ellas para evitar ambigüedades. Por ello también se ha modificado la línea 9 para incluir la FQCN de la excepción. El último problema que nos encontramos es que Hibernate no nos indica en la excepción cuál ha sido la propiedad que ha fallado, así que no nos queda más remedio que usar el mensaje de la excepción mediante java.lang.Throwable#getLocalizedMessage()|getLocalizedMessage() para dar algo de información al usuario. Desgraciadamente el mensaje es muy poco adecuado para un usuario. En nuestro caso el resultado de llamar a java.lang.Throwable#getLocalizedMessage()|getLocalizedMessage() es: Duplicate entry 'lramirez' for key 'login'. La última opción que nos queda es analizar el mensaje Duplicate entry 'lramirez' for key 'login' para obtener el nombre de la columna ''login'' y el valor duplicado ''lramirez''. El problema es que sólo valdrá para MySQL y que si la propia base de datos decide cambiar el mensaje también podría dejar de funcionar.