generatedvalue java hibernate jpa sequence

java - generatedvalue - jpa sequence generator



Hibernate JPA Sequence(no Id) (13)

"No quiero usar un disparador ni ninguna otra cosa que no sea Hibernate para generar el valor de mi propiedad"

En ese caso, ¿qué le parece crear una implementación de UserType que genere el valor requerido y configurar los metadatos para usar ese UserType para la persistencia de la propiedad mySequenceVal?

¿Es posible usar una secuencia DB para alguna columna que no sea el identificador / no sea parte de un identificador compuesto ?

Estoy usando hibernate como proveedor jpa, y tengo una tabla que tiene algunas columnas que son valores generados (usando una secuencia), aunque no son parte del identificador.

Lo que quiero es usar una secuencia para crear un nuevo valor para una entidad, donde la columna de la secuencia NO es (parte de) la clave principal:

@Entity @Table(name = "MyTable") public class MyEntity { //... @Id //... etc public Long getId() { return id; } //note NO @Id here! but this doesn''t work... @GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen") @SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE") @Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true) public Long getMySequencedValue(){ return myVal; } }

Entonces cuando hago esto:

em.persist(new MyEntity());

Se generará la identificación, pero la propiedad mySequenceVal también será generada por mi proveedor de JPA.

Para aclarar las cosas: quiero que Hibernate genere el valor de la propiedad mySequencedValue . Sé que Hibernate puede manejar los valores generados por la base de datos, pero no quiero utilizar un disparador o cualquier otra cosa que no sea Hibernate para generar el valor de mi propiedad. Si Hibernate puede generar valores para claves primarias, ¿por qué no puede generar para una propiedad simple?


Arreglé la generación de UUID (o secuencias) con Hibernate usando la anotación @PrePersist :

@PrePersist public void initializeUUID() { if (uuid == null) { uuid = UUID.randomUUID().toString(); } }


Aunque este es un tema viejo, quiero compartir mi solución y espero obtener algunos comentarios al respecto. Tenga en cuenta que solo probé esta solución con mi base de datos local en algún caso de prueba JUnit. Entonces esta no es una característica productiva hasta el momento.

Resolví ese problema por mi introduciendo una anotación personalizada llamada Sequence sin propiedad. Es solo un marcador para campos a los que se debe asignar un valor a partir de una secuencia incrementada.

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Sequence { }

Usando esta anotación marqué mis entidades.

public class Area extends BaseEntity implements ClientAware, IssuerAware { @Column(name = "areaNumber", updatable = false) @Sequence private Integer areaNumber; .... }

Para mantener la independencia de la base de datos, introduje una entidad llamada SequenceNumber que contiene el valor actual de la secuencia y el tamaño del incremento. Elegí className como clave única para que cada clase de entidad obtenga su propia secuencia.

@Entity @Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) }) public class SequenceNumber { @Id @Column(name = "className", updatable = false) private String className; @Column(name = "nextValue") private Integer nextValue = 1; @Column(name = "incrementValue") private Integer incrementValue = 10; ... some getters and setters .... }

El último paso y el más difícil es un PreInsertListener que maneja la asignación del número de secuencia. Tenga en cuenta que utilicé la primavera como contenedor de frijoles.

@Component public class SequenceListener implements PreInsertEventListener { private static final long serialVersionUID = 7946581162328559098L; private final static Logger log = Logger.getLogger(SequenceListener.class); @Autowired private SessionFactoryImplementor sessionFactoryImpl; private final Map<String, CacheEntry> cache = new HashMap<>(); @PostConstruct public void selfRegister() { // As you might expect, an EventListenerRegistry is the place with which event listeners are registered // It is a service so we look it up using the service registry final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class); // add the listener to the end of the listener chain eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this); } @Override public boolean onPreInsert(PreInsertEvent p_event) { updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames()); return false; } private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames) { try { List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class); if (!fields.isEmpty()) { if (log.isDebugEnabled()) { log.debug("Intercepted custom sequence entity."); } for (Field field : fields) { Integer value = getSequenceNumber(p_entity.getClass().getName()); field.setAccessible(true); field.set(p_entity, value); setPropertyState(p_state, p_propertyNames, field.getName(), value); if (log.isDebugEnabled()) { LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value }); } } } } catch (Exception e) { log.error("Failed to set sequence property.", e); } } private Integer getSequenceNumber(String p_className) { synchronized (cache) { CacheEntry current = cache.get(p_className); // not in cache yet => load from database if ((current == null) || current.isEmpty()) { boolean insert = false; StatelessSession session = sessionFactoryImpl.openStatelessSession(); session.beginTransaction(); SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className); // not in database yet => create new sequence if (sequenceNumber == null) { sequenceNumber = new SequenceNumber(); sequenceNumber.setClassName(p_className); insert = true; } current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue()); cache.put(p_className, current); sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue()); if (insert) { session.insert(sequenceNumber); } else { session.update(sequenceNumber); } session.getTransaction().commit(); session.close(); } return current.next(); } } private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState) { for (int i = 0; i < propertyNames.length; i++) { if (propertyName.equals(propertyNames[i])) { propertyStates[i] = propertyState; return; } } } private static class CacheEntry { private int current; private final int limit; public CacheEntry(final int p_limit, final int p_current) { current = p_current; limit = p_limit; } public Integer next() { return current++; } public boolean isEmpty() { return current >= limit; } } }

Como se puede ver en el código anterior, el oyente utilizó una instancia SequenceNumber por clase de entidad y reserva un par de números de secuencia definidos por el valor incremental de la entidad SequenceNumber. Si se agota el número de secuencia, carga la entidad SequenceNumber para la clase objetivo y reserva los valores incrementValue para las próximas llamadas. De esta forma, no necesito consultar la base de datos cada vez que se necesita un valor de secuencia. Tenga en cuenta la sesión sin estado que se está abriendo para reservar el siguiente conjunto de números de secuencia. No puede usar la misma sesión que la entidad objetivo está actualmente persistente ya que esto llevaría a una ConcurrentModificationException en EntityPersister.

Espero que esto ayude a alguien.


Buscando respuestas a este problema, me encontré con este enlace

Parece que Hibernate / JPA no puede crear automáticamente un valor para sus propiedades sin id. La anotación @GeneratedValue solo se usa junto con @Id para crear números automáticos.

La anotación @GeneratedValue le dice a Hibernate que la base de datos está generando este valor.

La solución (o alternativa) sugerida en ese foro es crear una entidad separada con un Id generado, algo como esto:

@Entity public class GeneralSequenceNumber { @Id @GeneratedValue(...) private Long number; } @Entity public class MyEntity { @Id .. private Long id; @OneToOne(...) private GeneralSequnceNumber myVal; }


Como continuación, así es cómo lo hice funcionar:

@Override public Long getNextExternalId() { BigDecimal seq = (BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0); return seq.longValue(); }


Corro en la misma situación que tú y tampoco encontré respuestas serias si es básicamente posible generar propiedades no identificables con JPA o no.

Mi solución es llamar a la secuencia con una consulta JPA nativa para establecer la propiedad a mano antes de persistirla.

Esto no es satisfactorio, pero funciona como una solución por el momento.

Mario


Después de pasar horas, esto me ayudó a resolver mi problema:

Para Oracle 12c:

ID NUMBER GENERATED as IDENTITY

Para H2:

ID BIGINT GENERATED as auto_increment

También haz:

@Column(insertable = false)


Encontré esta nota específica en la sesión 9.1.9 Anotación GeneratedValue de la especificación JPA: "[43] Las aplicaciones portátiles no deberían usar la anotación GeneratedValue en otros campos o propiedades persistentes". Por lo tanto, supongo que no es posible generar automáticamente valor para valores de clave no primarios al menos utilizando simplemente JPA.


Encontré que @Column(columnDefinition="serial") funciona perfecto pero solo para PostgreSQL. Para mí, esta fue la solución perfecta, porque la segunda entidad es la opción "fea".


Esto no es lo mismo que usar una secuencia. Cuando usa una secuencia, no está insertando ni actualizando nada. Simplemente está recuperando el siguiente valor de secuencia. Parece que Hibernate no lo admite.


He estado en una situación como tú (secuencia JPA / Hibernate para el campo no Id) y terminé creando un disparador en mi esquema db que agrega un número de secuencia único en la inserción. Nunca lo hice funcionar con JPA / Hibernate


Hibernate definitivamente apoya esto. De los documentos:

"Las propiedades generadas son propiedades que tienen sus valores generados por la base de datos. Normalmente, las aplicaciones Hibernate necesitaban actualizar los objetos que contenían cualquier propiedad para la cual la base de datos generaba valores. Sin embargo, al marcar las propiedades como generadas, la aplicación delega esta responsabilidad a Hibernate. Básicamente, cada vez que Hibernate emite un INSERT o ACTUALIZACIÓN SQL para una entidad que tiene propiedades generadas definidas, emite inmediatamente una selección para recuperar los valores generados ".

Para las propiedades generadas solo en la inserción, su asignación de propiedades (.hbm.xml) se vería así:

<property name="foo" generated="insert"/>

Para las propiedades generadas al insertar y actualizar su mapeo de propiedades (.hbm.xml) se vería así:

<property name="foo" generated="always"/>

Lamentablemente, no conozco JPA, por lo que no sé si esta característica está expuesta a través de JPA (sospecho que posiblemente no)

Alternativamente, debería poder excluir la propiedad de inserciones y actualizaciones, y luego llamar "manualmente" a session.refresh (obj); después de haberlo insertado / actualizado para cargar el valor generado de la base de datos.

Así es como excluiría que la propiedad se use en las instrucciones de inserción y actualización:

<property name="foo" update="false" insert="false"/>

De nuevo, no sé si JPA expone estas características de Hibernate, pero Hibernate las admite.


Sé que esta es una pregunta muy antigua, pero se muestra en primer lugar después de los resultados y jpa ha cambiado mucho desde la pregunta.

La forma correcta de hacerlo ahora es con la anotación @Generated . Puede definir la secuencia, establecer el valor predeterminado en la columna para esa secuencia y luego asignar la columna como:

@Generated(GenerationTime.INSERT) @Column(name = "column_name", insertable = false)