Guía de referencia:
Modelo |
Vista |
Datos tabulares |
Mapeo objeto/relacional |
Controladores |
Aplicación |
Personalización
Con el mapeo objeto relacional declaramos en que tablas y columnas de nuestra base de datos relacional se guarda la información de nuestra entidad.
Las herramientas O/R nos permiten trabajar con objetos, en vez de con tablas y columnas y generan automáticamente el código SQL necesario para leer y actualizar la base de datos. De esta forma no necesitamos acceder directamente a la base de datos con SQL, pero para eso tenemos que definir con precisión como se mapean nuestras clases a nuestras tablas, y eso es lo que se hace en las anotaciones de mapeo JPA.
Las entidades OpenXava son entidades JPA, por lo tanto el mapeo objeto/relacional en OpenXava se hace mediante
Java Persistence API (JPA). Este capítulo muestra las técnicas más básicas y algunos casos especiales. Si queremos aprender más sobre JPA podemos consultar
la documentación de Hibernate Annotations (la implementación de JPA usada por OpenXava por defecto), o cualquier otro manual de JPA que queramos. OpenXava 6.1 o superior usa JPA 2.2.
Mapeo de entidad
La anotación
@Table especifica la tabla principal para la entidad. Se pueden especificar tablas adicionales usando
@SecondaryTable o
@SecondaryTables.
Si no se especifica
@Table para una entidad se aplicaran los valores por defecto.
Ejemplo:
@Entity
@Table(name="CLI", schema="XAVATEST")
public class Cliente {
Mapeo propiedad
La anotación
@Column se usa para especificar como mapear una propiedad persistente. Si no se especifica
@Column se aplican los valores por defecto.
Un ejemplo sencillo:
@Column(name="DESC", length=512)
private String descripcion;
Un ejemplo anotando el
getter:
@Column(name="DESC", nullable=false, length=512)
public String getDescripcion() { return descripcion; }
Otros ejemplos:
@Column(name="DESC",
columnDefinition="CLOB NOT NULL",
table="EMP_DETAIL")
@Lob
private String descripcion;
@Column(name="ORDER_COST", updatable=false, precision=12, scale=2)
private BigDecimal coste;
Mapeo de referencia
La anotación
@JoinColumn se usa para especificar el mapeo de una columna para una referencia.
Ejemplo:
@ManyToOne
@JoinColumn(name="CLI_ID")
private Cliente cliente;
Si necesitamos definir un mapeo para una clave foranea compuesta hemos de usar varias
@JoinColumn. En este caso tanto el atributo
name como
referencedColumnName tienen que especificarse en cada anotación
@JoinColumn.
Ejemplo:
@ManyToOne
@JoinColumn(name="FAC_AÑO", referencedColumnName="AÑO")
@JoinColumn(name="FAC_NUMERO", referencedColumnName="NUMERO")
private Factura factura;
Si usas una versión de OpenXava anterior a 6.1 (que usaba el viejo JPA 2.1) has de usar
@JoinColumns. Esta anotación agrupa anotaciones
@JoinColumn para la misma referencia.
Ejemplo:
@ManyToOne
@JoinColumns({ // Sólo necesario hasta OpenXava 6.0.2/JPA 2.1
@JoinColumn(name="FAC_AÑO", referencedColumnName="AÑO"),
@JoinColumn(name="FAC_NUMERO", referencedColumnName="NUMERO")
})
private Factura factura;
Mapeo de colección
Cuando usamos
@OneToMany para una colección el mapeo depende de la referencia usada en la otra parte de la asociación, es decir, normalmente no es necesario hacer nada. Pero si estamos usando
@ManyToMany, quizás nos sea útil declarar la tabla de unión (
@JoinTable), como sigue:
@ManyToMany
@JoinTable(name="CLIENTE_PROVINCIA",
joinColumns=@JoinColumn(name="CLIENTE"),
inverseJoinColumns=@JoinColumn(name="PROVINCIA")
)
private Collection<Provincia> provincias;
Si omitimos
@JoinTable se aplican los valores por defecto.
Es muy sencillo, usando varias anotaciones
@AttributeOverride, de esta forma:
@Embedded
@AttributeOverride(name="calle", column=@Column("DIR_CALLE"))
@AttributeOverride(name="codigoPostal", column=@Column("DIR_CP"))
@AttributeOverride(name="poblacion", column=@Column("DIR_POB"))
@AttributeOverride(name="pais", column=@Column("DIR_PAIS"))
private Direccion direccion;
Con un OpenXava anterior a 6.1 (JPA 2.) has de usar
@AttributeOverrides:
@Embedded
@AttributeOverrides({ // Sólo hasta OpenXava 6.0.2/JPA 2.1
@AttributeOverride(name="calle", column=@Column("DIR_CALLE")),
@AttributeOverride(name="codigoPostal", column=@Column("DIR_CP")),
@AttributeOverride(name="poblacion", column=@Column("DIR_POB")),
@AttributeOverride(name="pais", column=@Column("DIR_PAIS"))
})
private Direccion direccion;
Si no usamos
@AttributeOverride se asumen valores por defectos.
Conversión de tipo
La conversión de tipos entre Java y la base de datos relacional es un trabajo de la implementación de JPA (OpenXava usa Hibernate por defecto). Normalmente, la conversión de tipos por defecto es buena para la mayoría de los casos, pero si trabajamos con bases de datos legadas quizás necesitemos algunos de los trucos que aquí se muestran.
Dado que OpenXava usa la facilidad de conversión de tipos de Hibernate podemos aprender más en la documentación de
Hibernate.
Conversión de propiedad
Cuando el tipo de una propiedad Java y el tipo de su columna correspondiente en la base de datos no coincide necesitamos escribir un
Hibernate Type para poder hacer nuestra conversión de tipo personalizada.
Por ejemplo, si tenemos una propiedad de tipo
String [], y queremos almacenar su valor concatenándolo en una sola columna de base de datos de tipo VARCHAR. Entonces tenemos que declarar la conversión para nuestra propiedad de esta manera:
@Type(type="org.openxava.test.types.RegionesType")
private String [] regiones;
La lógica de conversión en RegionesType es:
package org.openxava.test.types;
import java.io.*;
import java.sql.*;
import org.apache.commons.logging.*;
import org.hibernate.*;
import org.hibernate.usertype.*;
import org.hibernate.engine.spi.*; // A partir de OpenXava 5.3 que usa Hibernate 4.3
import org.openxava.util.*;
/**
*
* @author Javier Paniza
*/
public class RegionesType implements UserType { // 1
public int[] sqlTypes() {
return new int[] { Types.VARCHAR };
}
public Class returnedClass() {
return String[].class;
}
public boolean equals(Object obj1, Object obj2) throws HibernateException {
return Is.equal(obj1, obj2);
}
public int hashCode(Object obj) throws HibernateException {
return obj.hashCode();
}
// El argumento SessionImplementor a partir de OpenXava 5.3 que usa Hibernate 4.3
// SharedSessionContractImplementor en lugar de SessionImplementor a partir de OpenXava 6.1 que usa Hibernate 5.3
public Object nullSafeGet(ResultSet resultSet, String[] names, SharedSessionContractImplementor implementor, Object owner) // 2
throws HibernateException, SQLException
{
Object o = resultSet.getObject(names[0]);
if (o == null) return new String[0];
String dbValue = (String) o;
String [] javaValue = new String [dbValue.length()];
for (int i = 0; i < javaValue.length; i++) {
javaValue[i] = String.valueOf(dbValue.charAt(i));
}
return javaValue;
}
// El argumento SessionImplementor a partir de OpenXava 5.3 que usa Hibernate 4.3
// SharedSessionContractImplementor en lugar de SessionImplementor a partir de OpenXava 6.1 que usa Hibernate 5.3
public void nullSafeSet(PreparedStatement ps, Object value, int index, SharedSessionContractImplementor implementor) // 3
throws HibernateException, SQLException
{
if (value == null) {
ps.setString(index, "");
return;
}
String [] javaValue = (String []) value;
StringBuffer dbValue = new StringBuffer();
for (int i = 0; i < javaValue.length; i++) {
dbValue.append(javaValue[i]);
}
ps.setString(index, dbValue.toString());
}
public Object deepCopy(Object obj) throws HibernateException {
return obj == null?null:((String []) obj).clone();
}
public boolean isMutable() {
return true;
}
public Serializable disassemble(Object obj) throws HibernateException {
return (Serializable) obj;
}
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}
El conversor de tipo ha de implementar
org.hibernate.usertype.UserType (1). Los métodos principales son
nullSafeGet (2) para leer de la base de datos y convertir a Java, y
nullSafeSet (3) para escribir el valor Java en la base de datos.
OpenXava tiene conversores de tipo de Hibernate genéricos en el paquete
org.openxava.types listos para usar. Uno de ellos es
EnumLetterType, que permite mapear propiedades de tipo
enum. Por ejemplo, si tenemos una propiedad como esta:
private Distancia distancia;
public enum Distancia { LOCAL, NACIONAL, INTERNACIONAL };
En esta propiedad Java 'LOCAL' es 1, 'NATIONAL' es 2 and 'INTERNATIONAL' es 3 cuando la propiedad se almacena en la base de datos. Pero, ¿qué ocurre, si en la base de datos se almacena una única letra ('L', 'N' or 'I')? En este caso podemos usar
EnumLetterType de esta forma:
@Type(type="org.openxava.types.EnumLetterType",
parameters={
@Parameter(name="letters", value="LNI"),
@Parameter(name="enumType", value="org.openxava.test.modelo.Albaran$Distancia")
}
)
private Distancia distancia;
public enum Distancia { LOCAL, NACIONAL, INTERNACIONAL }
Al poner 'LNI' como valor para
letters, hace corresponder la 'L' con 1, la 'N' con 2 y la 'I' con 3. Vemos como el que se puedan configurar propiedades del conversor de tipos nos permite hacer conversores reutilizables.
Conversión con múltiples columnas
Con
CompositeUserType podemos hacer que varias columnas de la tabla de base de datos correspondan a una propiedad en Java. Esto es útil, por ejemplo cuando tenemos propiedades cuyo tipo Java son clases definidas por nosotros que tienen a su vez varias propiedades susceptibles de ser almacenadas, y también se usa mucho cuando nos enfrentamos a esquemas de bases de datos legados.
Un ejemplo típico sería usar el conversor genérico
Date3Type, que permite almacenar en la base de datos 3 columnas y en Java una propiedad
java.util.Date.
@Type(type="org.openxava.types.Date3Type")
@Columns(columns = {
@Column(name="AÑOENTREGA"),
@Column(name="MESENTREGA"),
@Column(name="DIAENTREGA")
})
private java.util.Date fechaEntrega;
DIAENTREGA, MESENTREGA y AÑOENTREGA son las tres columnas que en la base de datos guardan la fecha de entrega. Y aquí
Date3Type:
package org.openxava.types;
import java.io.*;
import java.sql.*;
import org.hibernate.*;
import org.hibernate.engine.*; // Hasta OpenXava 5.2.x
import org.hibernate.type.*; // A partir de OpenXava 5.3 que usa Hibernate 4.3
import org.hibernate.usertype.*;
import org.openxava.util.*;
/**
* In java a <tt>java.util.Date</tt> and in database 3 columns of
* integer type. <p>
*
* @author Javier Paniza
*/
public class Date3Type implements CompositeUserType { // 1
public String[] getPropertyNames() {
return new String[] { "year", "month", "day" };
}
public Type[] getPropertyTypes() {
// return new Type[] { Hibernate.INTEGER, Hibernate.INTEGER, Hibernate.INTEGER }; // Antes OpenXava 5.3/Hibernate 4.3
return new Type[] { IntegerType.INSTANCE, IntegerType.INSTANCE, IntegerType.INSTANCE }; // A partir de OpenXava 5.3/Hibernate 4.3
}
public Object getPropertyValue(Object component, int property) throws HibernateException { // 2
java.util.Date date = (java.util.Date) component;
switch (property) {
case 0:
return Dates.getYear(date);
case 1:
return Dates.getMonth(date);
case 2:
return Dates.getYear(date);
}
throw new HibernateException(XavaResources.getString("date3_type_only_3_properties"));
}
public void setPropertyValue(Object component, int property, Object value)
throws HibernateException // 3
{
java.util.Date date = (java.util.Date) component;
int intValue = value == null?0:((Number) value).intValue();
switch (property) {
case 0:
Dates.setYear(date, intValue);
case 1:
Dates.setMonth(date, intValue);
case 2:
Dates.setYear(date, intValue);
}
throw new HibernateException(XavaResources.getString("date3_type_only_3_properties"));
}
public Class returnedClass() {
return java.util.Date.class;
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x==y) return true;
if (x==null || y==null) return false;
return !Dates.isDifferentDay((java.util.Date) x, (java.util.Date) y);
}
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
// SharedSessionContractImplementor en lugar de SessionImplementor a partir de OpenXava 6.1 que usa Hibernate 5.3
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException // 4
{
/* Antes OpenXava 5.3/Hibernate 4.3
Number year = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[0] );
Number month = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[1] );
Number day = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[2] );
*/
// A partir de OpenXava 5.3/Hibernate 4.3
Number year = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[0], session, owner);
Number month = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[1], session, owner );
Number day = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[2], session, owner );
int iyear = year == null?0:year.intValue();
int imonth = month == null?0:month.intValue();
int iday = day == null?0:day.intValue();
return Dates.create(iday, imonth, iyear);
}
// SharedSessionContractImplementor en lugar de SessionImplementor a partir de OpenXava 6.1 que usa Hibernate 5.3
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException // 5
{
java.util.Date d = (java.util.Date) value;
/* Antes OpenXava 5.3/Hibernate 4.3
Hibernate.INTEGER.nullSafeSet(st, Dates.getYear(d), index);
Hibernate.INTEGER.nullSafeSet(st, Dates.getMonth(d), index + 1);
Hibernate.INTEGER.nullSafeSet(st, Dates.getDay(d), index + 2);
*/
// A partir de OpenXava 5.3/Hibernate 4.3
IntegerType.INSTANCE.nullSafeSet(st, Dates.getYear(d), index, session);
IntegerType.INSTANCE.nullSafeSet(st, Dates.getMonth(d), index + 1, session);
IntegerType.INSTANCE.nullSafeSet(st, Dates.getDay(d), index + 2, session);
}
public Object deepCopy(Object value) throws HibernateException {
java.util.Date d = (java.util.Date) value;
if (value == null) return null;
return (java.util.Date) d.clone();
}
public boolean isMutable() {
return true;
}
// SharedSessionContractImplementor en lugar de SessionImplementor a partir de OpenXava 6.1 que usa Hibernate 5.3
public Serializable disassemble(Object value, SharedSessionContractImplementor session)
throws HibernateException
{
return (Serializable) deepCopy(value);
}
// SharedSessionContractImplementor en lugar de SessionImplementor a partir de OpenXava 6.1 que usa Hibernate 5.3
public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner)
throws HibernateException
{
return deepCopy(cached);
}
// SharedSessionContractImplementor en lugar de SessionImplementor a partir de OpenXava 6.1 que usa Hibernate 5.3
public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner)
throws HibernateException
{
return deepCopy(original);
}
}
Como se ve el conversor de tipo implementa
CompositeUserType (1). Los métodos clave son
getPropertyValue (2) y
setPropertyValue (3) para coger y poner valores en las propiedades del objeto del tipo compuesto, y
nullSafeGet (4) y
nullSafeSet (5) para leer y grabar este objeto en la base de datos.
Conversión de referencia
La conversión de referencias no se soporta directamente por Hibernate. Pero en alguna circunstancias extremas puede ser que necesitemos hacer conversión de referencias. En esta sección se explica como hacerlo.
Por ejemplo, puede que tengamos una referencia a permiso de conducir usando dos columnas, PERMISOCONDUCIR_NIVEL y PERMISOCONDUCIR_TIPO, y la columna PERMISOCONDUCIR_TIPO no admita nulos, pero es posible que el objeto puede no tener permiso de conducir, en cuyo caso la columna PERMISOCONDUCIR_TIPO almacena una cadena vacía. Esto no es algo normal si nosotros diseñamos la base de datos usando claves foráneas, pero si la base de datos fue diseñada por un programador RPG, por ejemplo, esto se habrá hecho de esta forma, porque los programadores RPG no están acostumbrados a lidiar con nulos.
Es decir, necesitamos una conversión para PERMISOCONDUCIR_TIPO, para transformar el nulo en una cadena vacía. Esto se puede conseguir con un código como este:
// Aplicamos conversión (nulo en una cadena vacía) a la columna PERMISOCONDUCIR_TIPO
// Para hacerlo, creamos permisoConducir_nivel y permisoConducir_tipo
// Hacemos JoinColumns no insertable ni modificable, modificamos el método get/setPermisoConducir
// y creamos un método conversionPermisoConducir().
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({ // 1
@JoinColumn(name="PERMISOCONDUCIR_NIVEL", referencedColumnName="NIVEL",
insertable=false, updatable=false),
@JoinColumn(name="PERMISOCONDUCIR_TIPO", referencedColumnName="TIPO",
insertable=false, updatable=false)
})
private PermisoConducir permisoConducir;
private Integer permisoConducir_nivel; // 2
private String permisoConducir_tipo; // 2
public PermisoConducir getPermisoConducir() { // 3
// De esta manera porque la columna tipo de permiso de conducir no admite nulos
try {
if (permisoConducir != null) permisoConducir.toString(); // para forzar la carga
return permisoConducir;
}
catch (EntityNotFoundException ex) {
return null;
}
}
public void setPermisoConducir(PermisoConducir permiso) { // 4
// De esta manera porque la columna tipo de permiso de conducir no admite nulos
this.permisoConducir = permiso;
this.permisoConducir_nivel = permiso==null?null:permiso.getNivel();
this.permisoConducir_tipo = permiso==null?null:permiso.getTipo();
}
@PrePersist @PreUpdate
private void conversionPermisoConducir() { // 5
if (this.permisoConducir_tipo == null) this.permisoConducir_tipo = "";
}
Lo primero poner
@JoinColumns con
insertable=false y
updatable=false en todas las
@JoinColumn (1), de esta manera la referencia es leida de la base de datos, pero no escrita. También tenemos que definir propiedades planas para almacenar la clave foránea de la referencia (2).
Ahora tenemos que escribir un
getter,
getPermisoConducir() (3), para devolver nulo cuand la referencia no se encuentre, y un
setter,
setPermisoConducir() (4), para asignar la clave de la referencia a las propiedades planas correspondientes.
Finalmente, hemos de escribir un
método de retrollamada,
conversionPermisoConducir() (5), para hacer el trabajo de conversión. Este método será automáticamente ejecutado al crear y actualizar.
Este ejemplo enseña como es posible envolver bases de datos legadas simplemente usando un poco de programación y algunos recursos básicos de JPA.
Restricciones de valor único
Desde la v4.9 Openxava permite personalizar los mensajes de las restricciones declaradas en el elemento
uniqueConstraints de
@Table y
@SecondaryTable, así como de
@Column(unique=true). Para ello se debe tener en cuenta algunas consideraciones previas respecto a Hibernate (la implementación de JPA usada por defecto en Openxava)
Ejemplo:
package org.openxava.test.model;
@Entity
@SecondaryTable(
name="APPLICATIONUSER_INFO",
uniqueConstraints={
@UniqueConstraint(name="not_repeat_user_info", columnNames={"name", "birthdate", "sex"})
}
)
public class ApplicationUser extends Identifiable {
@Required
@Column(length=8, unique=true) //not_repeat_nic
private String nic;
@Column(length=40, table="APPLICATIONUSER_INFO")
private String name;
@Column(length=40, table="APPLICATIONUSER_INFO")
private Date birthdate;
@Column(table="APPLICATIONUSER_INFO")
@Enumerated(EnumType.STRING)
private Sex sex;
public enum Sex { MALE, FEMALE }
...
}
- Al construir nuestra aplicación (ejecutando build.xml), Hibernate utilizará el dialecto declarado en el persistence.xml de tu proyecto, para generar un DDL que mapeará la estructura de tus clases a tablas de la base de datos. Si, por ejemplo, estás trabajando con una base de datos MySQL, mostrará:
create table APPLICATIONUSER_INFO (
birthdate datetime,
name varchar(40),
sex varchar(255),
id varchar(32) not null,
primary key (id), unique (name, birthdate, sex)
)
create table ApplicationUser (
id varchar(32) not null,
nic varchar(8) unique,
primary key (id)
)
alter table APPLICATIONUSER_INFO
add index FK375C9572BA846971 (id),
add constraint FK375C9572BA846971 foreign key (id) references ApplicationUser (id)
Como se observa Hibernate ha mapeado la estructura de nuestra clase
ApplicationUser, incluso ha creado la restricciones
unique (name, birthdate, sex) y
nic varchar(8) unique pero no ha asignado el nombre
(“no_repeat_user_info”) declarado en
@UniqueConstraint, ni existe un elemento en
@Column que nos permita dar nombre a la restricción
unique=true, dejando que el motor de base de datos asigne nombres por defecto.
- Al producirse una violación a cualquiera de las restricciones anterioriores, Hibernate administrará el error, lanzado por el motor de base de datos, creando una org.hibernate.exception.ConstraintViolationException o en algunos casos una org.hibernate.exception.GenericJDBCException -como es el de HSQL-. ConstraintViolationException tiene una propiedad constraintName que es asignada por Hibernate después de extraer el nombre de la restricción que tiene la base de datos.
- Es el dialecto definido en tu proyecto el encargado de extraer el nombre de la restricción que será asignado al contraintName. Ésto lo hace por medio del método extractConstraintName(SQLException sqle) de la interfaz ViolatedConstraintNameExtracter. Pero sucede que los dialectos proporcionados por Hibernate no siempre realizan adecuadamente la extracción del constraintName. Para nuestro ejemplo, el dialecto MySQL5Dialect -de Hibernate 3.6.10 que usa Openxava- ni implementa la interfaz ViolatedConstraintNameExtracter -Hibernate4 ya lo hace adecuadamente-.
Por lo tanto, si quisiéramos generar un mensaje personalizado para las restricciones anteriores, hacemos:
- Para mantener coherencia entre el nombre de la @UniqueConstraint y el nombre de la restricción en la base de datos, la que Hibernate administrará por medio del contraintName de ConstraintViolationException, mapeamos manualmente el nombre de la restricción en la base de datos:
create table APPLICATIONUSER_INFO (
birthdate datetime,
name varchar(40),
sex varchar(255),
id varchar(32) not null,
primary key (id),
unique key `not_repeat_user_info` (name, birthdate, sex)
)
- Igual para la restricción de @Column:
create table ApplicationUser (
id varchar(32) not null,
nic varchar(8),
primary key (id),
unique key `not_repeat_nic` (nic)
)
- Si el dialecto no es el adecuado lo redefinimos:
package dialect;
import java.sql.*;
import org.hibernate.dialect.*;
import org.hibernate.exception.*;
public class XMySQL5Dialect extends MySQL5Dialect {
public XMySQL5Dialect(){}
private static ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() {
public String extractConstraintName(SQLException sqle) {
try {
int sqlState = Integer.valueOf( JDBCExceptionHelper.extractSqlState(sqle)).intValue();
switch (sqlState) {
case 23000: return extractUsingTemplate("for key '","'", sqle.getMessage());
default: return null;
}
} catch (NumberFormatException nfe) {
return null;
}
}
};
@Override
public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() {
return EXTRACTER;
}
}
- Asignamos el nuevo dialecto en el persistence.xml de nuestro proyecto.
...
<property name="hibernate.dialect"value="dialect.XMySQL5Dialect"/>
...
- Finalmente, declaramos los nombres asignados a las restricciones como identificadores de mensajes en el archivo i18n de nuestra aplicación.
Otros dialectos
import java.sql.*;
import org.hibernate.dialect.*;
import org.hibernate.exception.*;
public class XPostgreSQLDialect extends PostgreSQLDialect {
public XPostgreSQLDialect(){}
private static ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() {
public String extractConstraintName(SQLException sqle) {
try {
int sqlState = Integer.valueOf( JDBCExceptionHelper.extractSqlState(sqle)).intValue();
switch (sqlState) {
// CHECK VIOLATION
case 23514: return extractUsingTemplate("violates check constraint \"","\"", sqle.getMessage());
// UNIQUE VIOLATION
case 23505:
if (sqle.getMessage().indexOf("violates unique constraint \"") > -1)
return extractUsingTemplate("violates unique constraint \"","\"", sqle.getMessage());
else if (sqle.getNextException() != null )
return extractConstraintName(sqle.getNextException());
else
return "UNIQUE_CONSTRAINT_VIOLATION_UNKNOWN";
// FOREIGN KEY VIOLATION
case 23503: return extractUsingTemplate("violates foreign key constraint \"","\"", sqle.getMessage());
// NOT NULL VIOLATION
case 23502: return extractUsingTemplate("null value in column \"","\" violates not-null constraint", sqle.getMessage());
// RESTRICT VIOLATION
case 23001: return null;
// ALL OTHER
default: return null;
}
} catch (NumberFormatException nfe) {
return null;
}
}
};
@Override
public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() {
return EXTRACTER;
}
}
import java.sql.*;
import org.hibernate.dialect.*;
import org.hibernate.exception.*;
public class XSQLServerDialect extends SQLServer2008Dialect {
public XSQLServerDialect() {}
private static ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() {
public String extractConstraintName(SQLException sqle) {
try {
int sqlState = Integer.valueOf(JDBCExceptionHelper.extractSqlState(sqle)).intValue();
switch (sqlState) {
case 23000:
return extractUsingTemplate("UNIQUE KEY '", "'", sqle.getMessage());
default:
return null;
}
} catch (NumberFormatException nfe) {
return null;
}
}
};
@Override
public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() {
return EXTRACTER;
}
}