Superclase abstracta polimórfica @GeneratedValue sobre MySQL

En una aplicación Spring MVC que utiliza Hibernate y MySQL, tengo una superclase abstracta BaseEntity que administra los valores de los ID para todas las demás entidades en el modelo. El campo id usa @GeneratedValue . Me encuentro con un problema cada vez que mi código intenta guardar cualquiera de las subclases que extienden BaseEntity . El problema viene con la elección de GenerationType para @GeneratedValue .

En cada lugar de mi código donde una subclase de BaseEntity intenta guardar en la base de datos MySQL subyacente, BaseEntity el siguiente error:

 ERROR SqlExceptionHelper - Table 'docbd.hibernate_sequences' doesn't exist 

He leído muchas publicaciones sobre esto en SO y en Google, pero o bien tratan con otras bases de datos (no con MySQL) o no tratan con superclases abstractas. No puedo resolver el problema usando GenerationType.IDENTITY porque estoy usando una superclase abstracta para administrar los campos de id para todas las entidades en el modelo. Del mismo modo, no puedo usar GenerationType.SEQUENCE porque MySQL no admite secuencias.

Entonces, ¿cómo soluciono este problema?

Aquí está el código para BaseEntity.java :

 @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class BaseEntity { @Id @GeneratedValue(strategy = GenerationType.TABLE) protected Integer id; public void setId(Integer id) {this.id = id;} public Integer getId() {return id;} public boolean isNew() {return (this.id == null);} } 

Aquí hay un ejemplo del código para una de las entidades que extiende BaseEntity :

 @Entity @Table(name = "ccd") public class CCD extends BaseEntity{ //other stuff } 

Aquí está el DDL:

 CREATE TABLE IF NOT EXISTS ccd( id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, #other stuff )engine=InnoDB;SHOW WARNINGS; 

Aquí está el código JPQL en el DAO:

  @Override @Transactional public void saveCCD(CCD ccd) { if (ccd.getId() == null) { System.out.println("[[[[[[[[[[[[ about to persist CCD ]]]]]]]]]]]]]]]]]]]]"); this.em.persist(ccd); this.em.flush(); } else { System.out.println("]]]]]]]]]]]]]]]]]] about to merge CCD [[[[[[[[[[[[[[[[[[[[["); this.em.merge(ccd); this.em.flush(); } } 

EDITAR:

La razón por la que no puedo usar @MappedSuperClass en esta situación es que necesito tener relaciones ManyToOne que permitan el uso de subtipos múltiples de manera intercambiable. Mira la clase AccessLog continuación como un ejemplo. Tiene una actor_entity y una target_entity . Puede haber muchos tipos de entidades de actor y muchos tipos de entidades de destino, pero todas heredan de BaseEntity . Esta herencia permite que la tabla de datos subyacente de los accesslogs en MySQL solo tenga un campo actor_entity_id y solo un campo target_entity_id lugar de tener que tener varios campos para cada uno. Cuando cambio @Entity sobre BaseEntity a @MappedSuperClass , se @MappedSuperClass un error diferente que indica que AccessLog no puede encontrar BaseEntity . BaseEntity necesita la anotación @Entity para que AccessLog tenga propiedades polimórficas.

 @Entity @Table(name = "accesslogs") public class AccessLog extends BaseEntity{ @ManyToOne @JoinColumn(name = "actorentity_id") private BaseEntity actor_entity; @ManyToOne @JoinColumn(name = "targetentity_id") private BaseEntity target_entity; @Column(name="action_code") private String action; //getters, setters, & other stuff } 

SEGUNDA EDICION

Según la sugerencia de JBNizet, creé una tabla hibernate_sequences de la siguiente manera:

 CREATE TABLE IF NOT EXISTS hibernate_sequences( sequence_next_hi_value int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY )engine=InnoDB;SHOW WARNINGS; 

Pero ahora recibo el siguiente error:

 Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'sequence_name' in 'where clause' 

Aquí está el sql de hibernación que causa el error, seguido por las siguientes 2 líneas del rastreo de stack:

 Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'BaseEntity' for update ERROR MultipleHiLoPerTableGenerator - HHH000351: Could not read or init a hi value com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'sequence_name' in 'where clause' 

¿Cómo resuelvo esto?

Como usa el generador de identificador TABLE, necesita crear esa tabla. Si no está utilizando los generadores de identificadores mejorados , es probable que use el MultipleHiLoPerTableGenerator .

El MultipleHiLoPerTableGenerator puede usar una tabla para todos los generadores de identificador de tabla.

Mi sugerencia es tomar la tabla ddl de tus pruebas de integración, en caso de que uses hbmddl para construir el esquema de prueba. Si usa flyway o liquibase para realizar pruebas, puede agregar un complemento maven para generar el esquema ddl.

Una vez que tenga el esquema, debe tomar el comando create table exacto y agregarlo a su base de datos MySQL.

Qué desastre … AUTO_INCREMENT es la secuencia oculta de MySQL. El problema radical es que MySQL no puede insertar y devolver el PK al mismo tiempo, pero Hibernate lo necesita al INSERT una nueva Entidad.

Los problemas con los que te encuentras

  1. Si Hibernate guarda una nueva Entidad, intenta establecer de manera inmediata la Id. En la nueva EntityBean. Por lo tanto, hibernate debe leer qué ID usará la base de datos antes de hibernate, y guardar la nueva tupla en la tabla.
  2. Si tiene varios servidores que acceden a la base de datos, deberá dejar que la fábrica de sesiones de hibernate decida utilizar la secuencia incorporada (AUTO-INCREMENT) o dejar que hibernate decida ( GenerationType.AUTO / GenerationType.IDENTITY ) qué tan grande sea el rango abierto de reservado. PK’s es (trabajo de un DB-Architect). (Tenemos alrededor de 20 servidores en una Base de datos, por lo que en una tabla bien utilizada usamos una PK-distancia de +100). Si solo un servidor tiene acceso a la base de datos GenerationType.TABLE será correcto.

Hibernate debe calcular la siguiente identificación usted mismo usando max(*)+1 pero:

  • ¿Qué pasa si dos solicitudes piden max(*)+1 al mismo tiempo / con el mismo resultado? Derecha: el último bash de insert fallará.

Por lo tanto, debe tener una Tabla LAST_IDS en la base de datos que almacene los últimos Table-PK’s. Si desea agregar uno, debe seguir estos pasos:

  1. Inicie la transacción de lectura optimista.
  2. SELECCIONE MAX (ID_idirección) DESDE LAST_IDS
  3. almacenar el máximo en una variable java, es decir: $ OldID.
  4. $ NewID = $ OldID + 1. (+100 en locking pesimista)
  5. ACTUALIZAR LAST_IDS SET address_id = $newID WHERE address_id = $oldID ?
  6. cometer la transacción de lectura optimista.
  7. si commit fue exitoso, almacena $newID en setID() en el HibernateBean que te gusta guardar.
  8. Finalmente, deje que Hibernate llame al inserto.

Esta es la única forma que conozco.

Por cierto: Hibernate-Entitys solo usará la herencia si la Base de datos admite herencia entre tablas como PostgreSQL u Oracle .