openxava / documentación / Mapeo objeto/relacional

Guía de referencia: Modelo | Vista | Datos tabulares | Mapeo objeto/relacional | Controladores | Aplicación | Personalización

Tabla de contenidos

Mapeo objeto/relacional
Mapeo de entidad
Mapeo propiedad
Mapeo de referencia
Mapeo de colección
Mapeo de referencia incrustada
Conversión de tipo
Conversión de propiedad
Conversión con múltiples columnas
Conversión de referencia
Restricciones de valor único
Otros dialectos
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.

Cuando usamos @ElementCollection (nuevo en v5.0) para una colección podemos usar @CollectionTable y @AttributeOverride, como sigue:
@ElementCollection
@CollectionTable(name="CASAS") // Usa "join column" por defecto
@AttributeOverride(name="calle",
    column=@Column(name="CASA_CALLE"))
@AttributeOverride(name="localidad",
    column=@Column(name="CASA_LOCALIDAD"))
@AttributeOverride(name="provincia",
    column=@Column(name="CASA_PROVINCIA"))
private Collection<Direccion> casasVacaciones;
Si usas una versión de OpenXava anterior a 6.1 (con JPA 2.1) has de agrupar las @AttributeOverride con @AttributeOverrides, así:
@ElementCollection
@CollectionTable(name="CASAS") // Usa "join column" por defecto
@AttributeOverrides({ // Sólo hasta OpenXava 6.0.2/JPA 2.1
    @AttributeOverride(name="calle",
        column=@Column(name="CASA_CALLE")),
    @AttributeOverride(name="localidad",
        column=@Column(name="CASA_LOCALIDAD")),
    @AttributeOverride(name="provincia",
        column=@Column(name="CASA_PROVINCIA"))
})
private Collection<Direccion> casasVacaciones;
Si omitimos @CollectionTable y @AttributeOverride se aplican los valores por defecto.

Mapeo de referencia incrustada

Una referencia incrustada contiene información que en el modelo relacional se guarda en la misma tabla que la entidad principal. Por ejemplo si tenemos un incrustable Direccion asociado a un Cliente, los datos de la dirección se guardan en la misma tabla que los del cliente. ¿Cómo se expresa eso con JPA?
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 }
   ...
}
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.

Por lo tanto, si quisiéramos generar un mensaje personalizado para las restricciones anteriores, hacemos:
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)
)
create table ApplicationUser (
   id varchar(32) not null,
   nic varchar(8),
   primary key (id),
   unique key `not_repeat_nic` (nic)
)
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;
    }
}
...
<property name="hibernate.dialect"value="dialect.XMySQL5Dialect"/>
...

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