Que tiene una entidad que tiene una NO-IDENTIFICACIÓN de campo que se deben establecer a partir de una secuencia.
En la actualidad, me fetch para el primer valor de la secuencia, guardarla en el lado del cliente, y calcular a partir de ese valor.

Sin embargo, estoy en busca de una «mejor» manera de hacer esto. He implementado una forma de buscar el siguiente secuencia de valor:

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

Sin embargo, de esta manera se reduce el rendimiento de forma significativa (creación de ~5000 objetos obtiene ralentizado por un factor de 3 de 5740ms a 13648ms ).

He intentado añadir un «falso» entidad:

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

    public long getId() {
        return id;
    }
}

Sin embargo, este enfoque no funciona tampoco (todos los Identificadores devueltos fueron 0).

Alguien puede aconsejarme cómo obtener el siguiente valor de secuencia usando Hibernate eficientemente?

Edición: Tras la investigación, he descubierto que llamar a Query query = session.createSQLQuery( "select nextval('mySequence')" ); es mucho más ineficiente que el uso de la @GeneratedValue– debido a Hibernar de alguna manera logra reducir el número de búsquedas que al acceder a la secuencia descrita por @GeneratedValue.

Por ejemplo, cuando creo 70,000 entidades, (así, con 70.000 claves principales obtenidos de la misma secuencia), tengo todo lo que necesito.

Sin EMBARGO , Hibernate sólo cuestiones 1404 select nextval ('local_key_sequence') comandos. NOTA: En el lado de la base de datos, el almacenamiento en caché se establece en 1.

Si trato de recuperar todos los datos manualmente, me llevará de 70.000 selecciona, por lo tanto una gran diferencia en el rendimiento. ¿Alguien sabe el funcionamiento interno de la Hibernación, y cómo reproducir manualmente?

InformationsquelleAutor iliaden | 2011-06-17

9 Comentarios

  1. 24

    Aquí es lo que funcionó para mí (específicos de Oracle, pero el uso de scalar parece ser la clave)

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

    Gracias a los carteles aquí: springsource_forum

    • Como una nota, también puede utilizar StandardBasicTypes.De LARGO en lugar de StandardBasicTypes.BIG_INTEGER…
  2. 20

    Usted puede usar el modo de Hibernación Dialecto de la API de Base de datos de la independencia como siga

    class SequenceValueGetter {
    private SessionFactory sessionFactory;
    // For Hibernate 3
    public Long getId(final String sequenceName) {
    final List<Long> ids = new ArrayList<Long>(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<Long> maxReturningWork = new ReturningWork<Long>() {
    @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;
    }
    }
    • Esto es hermoso, excepto que
    • +1 creo que esta es la única base de datos independiente de la respuesta publicada, que es lo que necesito. (Tenemos una aplicación usando Hibernate 4 que necesita para obtener un no-valor de id de una base de datos de la secuencia, el uso de HSQLDB para pruebas y Oracle para la oferta real.) El uso de java 7 a tratar-con-recursos puede acortar el código un poco. Tuve que usar un DatabaseMetaDataDialectResolutionInfoAdapter para envolver el resultado de connection.getMetaData() antes de pasar a resolveDialect(). @punitpatel gracias!
    • En realidad, tengo que añadir que este utiliza dialect.getSequenceNextValString(), que no funcionan para las bases de datos no apoyan las secuencias…
    • Puede que desee utilizar doReturningWork (que puede ser más reciente que el de 3) también, getSequenceNextValString(...) no cita, o de lo contrario, compruebe el nombre de la secuencia, y es vulnerable a la inyección en el momento de este comentario. Así que no se el nombre de la secuencia de entrada de usuario, lo cual es bastante raro de todos modos.
    • Gracias! Funciona de maravilla. +1
  3. 5

    He encontrado la solución:

    public class DefaultPostgresKeyServer
    {
    private Session session;
    private Iterator<BigInteger> iter;
    private long batchSize;
    public DefaultPostgresKeyServer (Session sess, long batchFetchSize)
    {
    this.session=sess;
    batchSize = batchFetchSize;
    iter = Collections.<BigInteger>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<BigInteger>) query.list().iterator();
    }
    return iter.next().longValue() ;
    }
    }
    • Sólo una nota: Como la clase indica esto funciona en PostgreSQL solo, porque la sintaxis de SQL es referente de Oracle.
  4. 3

    Si está utilizando Oracle, considere la posibilidad de especificar el tamaño de caché para la secuencia. Si son rutinariamente crear objetos en lotes de 5 KM, se puede ponerlo a 1000 o 5000. Lo hicimos para la secuencia utilizada para el sustituto de la clave principal y se sorprendió de que los tiempos de ejecución de un proceso ETL mano-escrito en Java caído a la mitad.

    No pude pegar el formato de código en comentario. Aquí está la secuencia DDL:

    create sequence seq_mytable_sid 
    minvalue 1 
    maxvalue 999999999999999999999999999 
    increment by 1 
    start with 1 
    cache 1000 
    order  
    nocycle;
    • que suena como un enfoque interesante. ¿Me pueden decir anotaciones que se utiliza para establecer la creación de lotes (y por lo tanto el lote a que la obtención de los valores de la secuencia)?
    • La que se especifica el tamaño de caché en la secuencia de DDL. No pude pegar el código en los comentarios, así que he editado la respuesta.
    • 1000 parece ser el necesario campo, pero ¿cómo puedo mapa en modo de Hibernación?
    • Si su anterior estrategia está funcionando, lento, el cambio de la secuencia de definición debe hacer la magia. En nuestro caso no hemos tenido que cambiar nada en la app, o hacer una nueva construcción.
    • Olaf: he investigado un poco más. Y descubrió algunos negra magia vudú. Ver la edición pregunta
    • Lo que yo veo. Mi experiencia fue que las secuencias utilizadas para generar un sustituto de la clave principal.

  5. 2

    Para obtener el id nuevo, todo lo que tienes que hacer es flush el gerente de la entidad. Ver getNext() método a continuación:

    @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();
    }
    }
    • ese es el problema – la secuencia NO es para una clave principal (NO el IDENTIFICADOR de campo). Aunque tengo que investigar sobre el uso de SequenceFetcher
    • El SequenceFether fue una clase extra de enfoque, pero hibernate con fuerza la asigna a una [no existe] de la tabla.
    • «em.flush()» me dio un error. Me lo quitaron y trabajó muy bien. Gracias!
  6. 1

    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();
    • Su teoría es correcta, y me ayudó, pero el SQL es vulnerable a la inyección. Eso nunca es bueno.
  7. 1

    Interesante lo que funciona para usted. Cuando he intentado la solución de un error se acercó, diciendo que «no coincide el Tipo: no se puede convertir de SQLQuery a la Consulta». –> por lo Tanto, mi solución parece:

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

    Con esa solución no funciona en problemas de rendimiento.

    Y no se olvide de reiniciar su valor, si sólo quería saber para propósitos de información.

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

    Aquí es la manera en que lo hago:

    @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);
  9. 0

    Su idea con el SequenceGenerator falso entidad es buena.

    @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")

    Es importante usar el parámetro con el nombre de la clave «sequence_name». Ejecutar una sesión de depuración en la hibernación de la clase SequenceStyleGenerator, configure » (…) el método en la línea final QualifiedName sequenceName = determineSequenceName( params, dialecto, jdbcEnvironment ); para ver más detalles acerca de cómo el nombre de la secuencia se calcula por Hibernate. Hay algunos valores predeterminados de ahí que también se podría utilizar.

    Después de la falsa entidad, he creado un CrudRepository:

    public interface SequenceRepository extends CrudRepository<SequenceGenerator, Long> {}

    En la Junit, que yo llamo el método save de la SequenceRepository.

    SequenceGenerator sequenceObject = new SequenceGenerator();
    SequenceGenerator resultado = sequenceRepository.guardar(sequenceObject);

    Si hay una mejor manera de hacer esto (tal vez el apoyo de un generador en cualquier tipo de campo en lugar de sólo Id), yo estaría más que feliz a usarlo en lugar de este «truco».

Dejar respuesta

Please enter your comment!
Please enter your name here