java - generatedvalue - jpa entity
Especifique manualmente el valor de una clave principal en la columna JPA @GeneratedValue (6)
Estoy teniendo una Entidad que tiene un campo clave / ID principal como el siguiente:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Esto funciona bien Estoy usando EclipseLink para crear el DDL-Schema, y la columna está correctamente creada así:
`id` bigint(20) NOT NULL AUTO_INCREMENT
Sin embargo, tengo varias entidades para las cuales sí quiero especificar el PK (es una pequeña aplicación que transfiere datos de una base de datos antigua a la nueva que estamos creando). Si especifico el ID para el POJO (usando setId(Long id)
) y lo mantengo, EclipseLink no lo guarda (es decir, el registro se guarda, pero el ID es generado automáticamente por eclipseLink).
¿Hay alguna manera de especificar manualmente el valor de una columna que tiene un @GeneratedValue?
Aquí algunas reflexiones sobre el tema:
Traté de evitar el problema al no usar @GeneratedValue en absoluto, sino que simplemente defino manualmente la columna para que sea AUTO_INCREMENTADA. Sin embargo, esto me obliga a proporcionar manualmente una ID siempre , ya que EclipseLink valida la clave principal (por lo que puede no ser nula, cero o un número negativo). El mensaje de excepción dice que debo especificar eclipselink.id_validation, sin embargo, esto no parece hacer ninguna diferencia ( @PrimaryKey(validation = IdValidation.NONE)
pero igual recibí el mismo mensaje).
Para aclarar: estoy usando EclipseLink (2.4.0) como proveedor de persistencia y no puedo abandonarlo (grandes porciones del proyecto dependen de sugerencias de consulta, anotaciones y clases específicas de eclipselink).
EDITAR (en respuesta a las respuestas):
Secuenciación personalizada: Intenté implementar mi propia secuencia. Intenté subclasar DefaultSequence, pero EclipseLink me dirá Internal Exception: org.eclipse.persistence.platform.database.MySQLPlatform could not be found
. Pero lo he comprobado: la clase está en el classpath.
Así que subclasé otra clase, NativeSequence:
public class MyNativeSequence extends NativeSequence {
public MyNativeSequence() {
super();
}
public MyNativeSequence(final String name) {
super(name);
}
@Override
public boolean shouldAlwaysOverrideExistingValue() {
return false;
}
@Override
public boolean shouldAlwaysOverrideExistingValue(final String seqName) {
return false;
}
}
Sin embargo, lo que obtengo es lo siguiente:
javax.persistence.RollbackException: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.java:102)
...
Caused by: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
at org.eclipse.persistence.exceptions.ValidationException.nullPrimaryKeyInUnitOfWorkClone(ValidationException.java:1451)
...
(la traza de la pila se acorta para mayor claridad). Este es el mismo mensaje que obtuve antes. ¿No debería subclasificar NativeSequence? Si es así, no sé qué implementar para los métodos abstractos en Sequence o StandardSequence.
También puede valer la pena señalar que, simplemente subclases (sin anular ningún método) la clase funciona como se esperaba. Sin embargo, el recuento falso en shouldAlwaysOverrideExistingValue(...)
no generará ningún valor en absoluto (pasé por el programa y getGeneratedValue()
no se llamó una vez).
Además, cuando inserto como 8 entidades de un cierto tipo dentro de una transacción, resultó en 11 registros en la base de datos (¿qué diablos ?!).
EDITAR (2012-09-01): Todavía no tengo una solución para el problema. Implementar mi propia secuencia no lo resolvió. Lo que necesito es una forma de no poder establecer un Id de forma explícita (para que se genere automáticamente) y poder establecer un Id de forma explícita (para que se use para la creación del registro en la base de datos).
Intenté definir la columna como auto_increment y omitir @GeneratedValue; sin embargo, Validation se activará y no me permitirá guardar dicha entidad. Si especifico un valor! = 0 y! = Cero, mysql se quejará por una clave primaria duplicada.
Me estoy quedando sin ideas y opciones para probar. ¿Alguna? (comenzando una recompensa)
Esto funciona con eclipselink. Creará una tabla separada para la secuencia, pero eso no debería representar un problema.
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="id", insertable=true, updatable=true, unique=true, nullable=false)
private Long id;
GenerationType.AUTO elegirá la estrategia de generación ideal. Dado que el campo se especifica como insertable y actualizable, se utilizará una estrategia de generación TABLE. Esto significa que eclipselink generará otra tabla que contiene el valor actual de la secuencia y generará la secuencia en lugar de delegarla en la base de datos. Como la columna se declara insertable, si id es nula cuando persiste, eclipselink generará el id. De lo contrario, se usará la identificación existente.
Me podría estar perdiendo algo obvio, pero ¿por qué no simplemente definir otra Entidad con la misma @Table(name=".....")
, con los mismos campos, pero hacer que no se genere el ID? Luego puede usar esa entidad para el código que copia los datos del antiguo DB al nuevo, y el que tiene el Id generado se puede usar para las creaciones normales que requieren la generación de id.
No puedo decir si funciona con EclipseLink, pero estamos usando Hibernate aquí y parece que no le importa.
Si usa la secuencia de TABLE, entonces EclipseLink le permitirá anular el valor (o SECUENCIA si su base de datos lo admite, MySQL no).
Para IDENTIDAD, ni siquiera estoy seguro de que MySQL le permita proporcionar su propio Id. Es posible que desee verificar esto. En general, nunca recomendaría usar IDENTIDAD, ya que no es compatible con la asignación previa.
Hay algunos problemas para permitir que IDENTITY proporcione la identificación o no. Una es que se necesitarán dos SQL de inserción diferentes dependiendo del valor de identificación, ya que para IDENTIDAD la identificación no puede estar en la inserción. Es posible que desee registrar un error para que IDENTITY soporte ID proporcionados por el usuario.
Debería poder seguir trabajando con su propia subclase Sequence o posiblemente con la subclase MySQLPlatform. Debería establecer su subclase MySQLPlatform utilizando la propiedad de unidad de persistencia "eclipselink.target-database".
Solución centrada en la base de datos para su problema:
Crea una columna auxiliar, que acepta valores de nulo en tu tabla. Retendrá tus identificadores asignados manualmente:
CREATE TABLE `test_table` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `manual_id` bigint(20) NULL, `some_other_field` varchar(200) NOT NULL, PRIMARY KEY(id) );
Asigne esta columna a un campo normal en su Entidad:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name="manual_id") private Integer manualId;
Cree un activador que establezca el ID de la tabla en el ID asignado manualmente si no es nulo:
DELIMITER // CREATE TRIGGER `test_table_bi` BEFORE INSERT ON `test_table` FOR EACH ROW BEGIN IF NEW.`manual_id` IS NOT NULL THEN SET NEW.`id` = NEW.`manual_id`; END IF; END;// DELIMITER;
Siempre use el
manualId
cuando necesite asignar una identificación personalizada. El disparador hará la magia para ti:testEntiy.setManualId(300); entityManager.persist(testEntity);
Después de la fase de importación de la base de datos, simplemente elimine el disparador, la columna auxiliar y su mapeo.
DROP TRIGGER `test_table_bi`; ALTER TABLE `test_table` DROP COLUMN `manual_id`;
Advertencia
Si especifica manualmente un ID mayor que el valor AUTO_INCREMENT
actual, el siguiente id
generado saltará al valor del ID más 1 asignado manualmente, por ejemplo:
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (50, ''Something'');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, ''Something else'');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (90, ''Something 2'');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, ''Something else 2'');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (40, ''Something 3'');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, ''Something else 3'');
Manejará los resultados:
+----+-----------+------------------+
| id | manual_id | some_other_field |
+----+-----------+------------------+
| 50 | 50 | Something |
| 51 | NULL | Something else |
| 90 | 90 | Something 2 |
| 91 | NULL | Something else 2 |
| 40 | 40 | Something 3 |
| 92 | NULL | Something else 3 |
+----+-----------+------------------+
Para evitar problemas, se recomienda establecer la columna AUTO_INCREMENT
para que comience con un número mayor que todos los ID existentes en su base de datos anterior, por ejemplo:
ALTER TABLE `test_table` AUTO_INCREMENT = 100000;
Usar GenerationType.SEQUENCE con PostgreSQL y EclipseLink funcionó para mí.
1) Cambiar
@GeneratedValue(strategy = GenerationType.IDENTITY)
por
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="step_id_seq")
@SequenceGenerator(name="step_id_seq", sequenceName="step_id_seq")
Ahora, puedes llamar a la secuencia usando NativeQuery:
return ((Vector<Integer>) em.createNativeQuery("select nextval(''step_id_seq'')::int").getSingleResult()).get(0);
y configure el Id. devuelto a su Entidad antes de llamar al método EntityManager.persist ().
Espero que no sea demasiado tarde!
Busque Custom Id Generator
http://blog.anorakgirl.co.uk/2009/01/custom-hibernate-sequence-generator-for-id-field/
tal vez esto podría ayudar.