Obtenez la valeur de séquence suivante à partir de la base de données en utilisant Hibernate

StackOverflow https://stackoverflow.com/questions/6386888

Question

J'ai une entité qui a un champ non ID qui doit être défini à partir d'une séquence. Actuellement, je récupére pour la première valeur de la séquence, je la stocke du côté du client et je calcule à partir de cette valeur.

Cependant, je cherche une "meilleure" façon de le faire. J'ai implémenté un moyen de récupérer la valeur de séquence suivante:

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

Cependant, cette manière réduit considérablement les performances (la création d'environ 5000 objets est ralenti d'un facteur de 3 - de 5740 ms à 13648 ms).

J'ai essayé d'ajouter une "fausse" entité:

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

    public long getId() {
        return id;
    }
}

Cependant, cette approche n'a pas fonctionné non plus (tous les ID retournés étaient 0).

Quelqu'un peut-il me conseiller comment récupérer la valeur de séquence suivante en utilisant efficacement Hibernate?

Éditer: Lors de l'enquête, j'ai découvert que l'appel Query query = session.createSQLQuery( "select nextval('mySequence')" ); est de beaucoup plus inefficace que d'utiliser le @GeneratedValue- à cause de l'hibernate en quelque sorte parvient à réduire le nombre de récupérations lors de l'accès à la séquence décrite par @GeneratedValue.

Par exemple, lorsque je crée 70 000 entités (donc avec 70 000 clés primaires récupérées à partir de la même séquence), j'ai tout ce dont j'ai besoin.

TOUTEFOIS , Hiberner uniquement les problèmes 1404 select nextval ('local_key_sequence') Commandes. Remarque: Du côté de la base de données, la mise en cache est définie sur 1.

Si j'essaie de récupérer toutes les données manuellement, cela me prendra 70 000 sélections, donc une énorme différence de performances. Quelqu'un connaît-il le fonctionnement interne de l'hibernate et comment le reproduire manuellement?

Était-ce utile?

La solution 3

J'ai trouvé la solution:

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() ;
        }

}

Autres conseils

Voici ce qui a fonctionné pour moi (spécifique à Oracle, mais en utilisant scalar semble être la clé)

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

    return ((BigInteger) query.uniqueResult()).longValue();
}

Merci aux affiches ici: springsource_forum

Vous pouvez utiliser l'API du dialecte Hibernate pour l'indépendance de la base de données comme suit

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;
    }

}

Si vous utilisez Oracle, envisagez de spécifier la taille du cache pour la séquence. Si vous créez régulièrement des objets en lots de 5k, vous pouvez simplement le régler sur un 1000 ou 5000. à moitié.

Je ne pouvais pas coller le code formaté en commentaire. Voici la séquence DDL:

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

Pour obtenir le nouvel identifiant, tout ce que vous avez à faire est flush le gestionnaire d'entité. Voir getNext() Méthode ci-dessous:

@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();

Intéressant, cela fonctionne pour vous. Lorsque j'ai essayé votre solution, une erreur est apparue, disant que "Type Mismatch: ne peut pas se convertir de SqlQuery en requête". -> Par conséquent, ma solution ressemble:

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

Avec cette solution, je n'ai pas rencontré de problèmes de performances.

Et n'oubliez pas de réinitialiser votre valeur, si vous vouliez simplement savoir à des fins d'information.

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

Le printemps 5 a des cours d'assistance intégrés pour cela:org / springframework / jdbc / support / incrémentateur

Voici la façon dont je le fais:

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

Votre idée avec la fausse entité SequenceGenerator est bonne.

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

Il est important d'utiliser le paramètre avec le nom de clé "Sequence_name". Exécutez une session de débogage sur la classe HiberNate Class SequenCyleGenerator, la méthode Configure (...) sur la ligne final QualifiedName SequenceName = déterminateSquenceName (Params, Dialect, JDBCenvironment); Pour voir plus de détails sur la façon dont le nom de séquence est calculé par Hibernate. Il y a des valeurs par défaut que vous pouvez également utiliser.

Après la fausse entité, j'ai créé une crudrepository:

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

Dans le Junit, j'appelle la méthode de sauvegarde de la SequenceRepository.

SequenceGenerator SequenceObject = new SequenceGenerator (); SequenceGenerator result = SequenceRepository.save (SequenceObject);

S'il existe une meilleure façon de le faire (peut-être prendre en charge un générateur sur n'importe quel type de champ au lieu d'identifier simplement), je serais plus qu'heureux de l'utiliser au lieu de cette "astuce".

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top