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