ottenere il prossimo valore di sequenza dal database usando Hibernate

Ho un’entity framework che ha un campo NON-ID che deve essere impostato da una sequenza. Attualmente, recupero il primo valore della sequenza, lo memorizzo sul lato del cliente e calcola da quel valore.

Tuttavia, sto cercando un modo “migliore” per farlo. Ho implementato un modo per recuperare il prossimo valore di sequenza:

public Long getNextKey() { Query query = session.createSQLQuery( "select nextval('mySequence')" ); Long key = ((BigInteger) query.uniqueResult()).longValue(); return key; } 

Tuttavia, questo modo riduce significativamente le prestazioni (la creazione di ~ 5000 oggetti viene rallentata di un fattore 3 – da 5740 ms a 13648 ms).

Ho provato ad aggiungere un’entity framework “falsa”:

 @Entity @SequenceGenerator(name = "sequence", sequenceName = "mySequence") public class SequenceFetcher { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence") private long id; public long getId() { return id; } } 

Tuttavia questo approccio non ha funzionato neanche (tutti gli ID restituiti erano 0).

    Qualcuno può consigliarmi come recuperare il prossimo valore di sequenza usando Hibernate in modo efficiente?

    Modifica: dopo un’indagine, ho scoperto che chiama Query query = session.createSQLQuery( "select nextval('mySequence')" ); è di gran lunga più inefficiente rispetto all’utilizzo di @GeneratedValue – poiché Hibernate in qualche modo riesce a ridurre il numero di fetch quando accede alla sequenza descritta da @GeneratedValue .

    Ad esempio, quando creo 70.000 quadro (quindi con 70.000 chiavi primarie recuperate dalla stessa sequenza), ottengo tutto ciò di cui ho bisogno.

    TUTTAVIA , Hibernate emette solo 1404 select nextval ('local_key_sequence') . NOTA: dal lato del database, la memorizzazione nella cache è impostata su 1.

    Se provo a recuperare tutti i dati manualmente, mi occorreranno 70.000 selezioni, quindi un’enorme differenza di prestazioni. Qualcuno conosce il funzionamento interno di Hibernate e come riprodurlo manualmente?

    Ecco cosa ha funzionato per me (specifico per Oracle, ma l’uso dello scalar sembra essere la chiave)

     Long getNext() { Query query = session.createSQLQuery("select MYSEQ.nextval as num from dual") .addScalar("num", StandardBasicTypes.BIG_INTEGER); return ((BigInteger) query.uniqueResult()).longValue(); } 

    Grazie ai poster qui: springsource_forum

    È ansible utilizzare l’API di Hibernate Dialect per l’indipendenza del database come segue

     class SequenceValueGetter { private SessionFactory sessionFactory; // For Hibernate 3 public Long getId(final String sequenceName) { final List ids = new ArrayList(1); sessionFactory.getCurrentSession().doWork(new Work() { public void execute(Connection connection) throws SQLException { DialectResolver dialectResolver = new StandardDialectResolver(); Dialect dialect = dialectResolver.resolveDialect(connection.getMetaData()); PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { preparedStatement = connection.prepareStatement( dialect.getSequenceNextValString(sequenceName)); resultSet = preparedStatement.executeQuery(); resultSet.next(); ids.add(resultSet.getLong(1)); }catch (SQLException e) { throw e; } finally { if(preparedStatement != null) { preparedStatement.close(); } if(resultSet != null) { resultSet.close(); } } } }); return ids.get(0); } // For Hibernate 4 public Long getID(final String sequenceName) { ReturningWork maxReturningWork = new ReturningWork() { @Override public Long execute(Connection connection) throws SQLException { DialectResolver dialectResolver = new StandardDialectResolver(); Dialect dialect = dialectResolver.resolveDialect(connection.getMetaData()); PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { preparedStatement = connection.prepareStatement( dialect.getSequenceNextValString(sequenceName)); resultSet = preparedStatement.executeQuery(); resultSet.next(); return resultSet.getLong(1); }catch (SQLException e) { throw e; } finally { if(preparedStatement != null) { preparedStatement.close(); } if(resultSet != null) { resultSet.close(); } } } }; Long maxRecord = sessionFactory.getCurrentSession().doReturningWork(maxReturningWork); return maxRecord; } } 

    Ho trovato la soluzione:

     public class DefaultPostgresKeyServer { private Session session; private Iterator iter; private long batchSize; public DefaultPostgresKeyServer (Session sess, long batchFetchSize) { this.session=sess; batchSize = batchFetchSize; iter = Collections.emptyList().iterator(); } @SuppressWarnings("unchecked") public Long getNextKey() { if ( ! iter.hasNext() ) { Query query = session.createSQLQuery( "SELECT nextval( 'mySchema.mySequence' ) FROM generate_series( 1, " + batchSize + " )" ); iter = (Iterator) query.list().iterator(); } return iter.next().longValue() ; } } 

    Se stai usando Oracle, considera di specificare la dimensione della cache per la sequenza. Se si creano oggetti in lotti di 5K, è ansible impostarlo su 1000 o 5000. Lo abbiamo fatto per la sequenza utilizzata per la chiave primaria surrogata e siamo rimasti sorpresi dal fatto che i tempi di esecuzione per un processo ETL scritti a mano in Java sono stati eliminati a metà.

    Non è stato ansible incollare il codice formattato nel commento. Ecco la sequenza DDL:

     create sequence seq_mytable_sid minvalue 1 maxvalue 999999999999999999999999999 increment by 1 start with 1 cache 1000 order nocycle; 

    Per ottenere il nuovo id, tutto ciò che devi fare è flush il gestore dell’ quadro. Vedi il metodo getNext() qui sotto:

     @Entity @SequenceGenerator(name = "sequence", sequenceName = "mySequence") public class SequenceFetcher { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence") private long id; public long getId() { return id; } public static long getNext(EntityManager em) { SequenceFetcher sf = new SequenceFetcher(); em.persist(sf); em.flush(); return sf.getId(); } } 

    POSTGRESQL

     String psqlAutoincrementQuery = "SELECT NEXTVAL(CONCAT(:psqlTableName, '_id_seq')) as id"; Long psqlAutoincrement = (Long) YOUR_SESSION_OBJ.createSQLQuery(psqlAutoincrementQuery) .addScalar("id", Hibernate.LONG) .setParameter("psqlTableName", psqlTableName) .uniqueResult(); 

    MYSQL

     String mysqlAutoincrementQuery = "SELECT AUTO_INCREMENT as id FROM information_schema.tables WHERE table_name = :mysqlTableName AND table_schema = DATABASE()"; Long mysqlAutoincrement = (Long) YOUR_SESSION_OBJ.createSQLQuery(mysqlAutoincrementQuery) .addScalar("id", Hibernate.LONG) .setParameter("mysqlTableName", mysqlTableName) .uniqueResult(); 

    Interessante funziona per te. Quando ho provato la tua soluzione, è emerso un errore che diceva “Tipo non corrispondente: imansible convertire da SQLQuery a Query”. -> Quindi la mia soluzione sembra:

     SQLQuery query = session.createSQLQuery("select nextval('SEQUENCE_NAME')"); Long nextValue = ((BigInteger)query.uniqueResult()).longValue(); 

    Con questa soluzione non ho riscontrato problemi di prestazioni.

    E non dimenticare di reimpostare il tuo valore, se solo volessi sapere a scopo informativo.

      --nextValue; query = session.createSQLQuery("select setval('SEQUENCE_NAME'," + nextValue + ")"); 

    Ecco come lo faccio:

     @Entity public class ServerInstanceSeq { @Id //mysql bigint(20) @SequenceGenerator(name="ServerInstanceIdSeqName", sequenceName="ServerInstanceIdSeq", allocationSize=20) @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ServerInstanceIdSeqName") public Long id; } ServerInstanceSeq sis = new ServerInstanceSeq(); session.beginTransaction(); session.save(sis); session.getTransaction().commit(); System.out.println("sis.id after save: "+sis.id); 

    La tua idea con l’entity framework falsa di SequenceGenerator è buona.

     @Id @GenericGenerator(name = "my_seq", strategy = "sequence", parameters = { @org.hibernate.annotations.Parameter(name = "sequence_name", value = "MY_CUSTOM_NAMED_SQN"), }) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq") 

    È importante utilizzare il parametro con il nome chiave “nome_segui”. Esegui una sessione di debug sulla class di ibernazione SequenceStyleGenerator, il metodo configure (…) sulla riga finale QualifiedName sequenceName = defineSequenceName (params, dialect, jdbcEnvironment); per vedere maggiori dettagli su come il nome della sequenza viene calcolato da Hibernate. Ci sono alcune impostazioni predefinite che puoi usare anche qui.

    Dopo l’ quadro falsa, ho creato un CrudRepository:

     public interface SequenceRepository extends CrudRepository {} 

    Nel Junit, chiamo il metodo di salvataggio del SequenceRepository.

    SequenceGenerator sequenceObject = new SequenceGenerator (); SequenceGenerator result = sequenceRepository.save (sequenceObject);

    Se c’è un modo migliore per farlo (forse il supporto per un generatore su qualsiasi tipo di campo invece di solo Id), sarei più che felice di usarlo invece di questo “trucco”.