it-swarm.com.de

Abrufen des nächsten Sequenzwerts aus der Datenbank im Ruhezustand

Ich habe eine Entität mit einem NON-ID-Feld, das aus einer Sequenz festgelegt werden muss. Derzeit rufe ich den ersten Wert der Sequenz ab, speichere ihn auf der Client-Seite und berechne aus diesem Wert.

Ich bin jedoch auf der Suche nach einem "besseren" Weg, dies zu tun. Ich habe eine Möglichkeit implementiert, den nächsten Sequenzwert abzurufen:

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

Auf diese Weise wird jedoch die Leistung erheblich reduziert (die Erstellung von ~ 5000 Objekten wird um den Faktor 3 verlangsamt - von 5740 ms auf 13648 ms).

Ich habe versucht, eine "gefälschte" Entität hinzuzufügen:

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

    public long getId() {
        return id;
    }
}

Dieser Ansatz hat jedoch auch nicht funktioniert (alle zurückgegebenen IDs waren 0).

Kann mir jemand raten, wie man den nächsten Sequenzwert mit Hibernate effizient abruft?

Edit: Bei einer Untersuchung habe ich festgestellt, dass der Aufruf von Query query = session.createSQLQuery( "select nextval('mySequence')" ); aufgrund von Hibernate irgendwie bei weitem ineffizienter ist als die Verwendung von @GeneratedValue schafft es, die Anzahl der Abrufe beim Zugriff auf die durch @GeneratedValue beschriebene Sequenz zu reduzieren.

Wenn ich beispielsweise 70.000 Entitäten erstelle (also 70.000 Primärschlüssel aus derselben Sequenz abgerufen werden), erhalte ich alles, was ich brauche.

JEDOCH , Hibernate gibt nur 1404select nextval ('local_key_sequence') Befehle aus. HINWEIS: Auf der Datenbankseite ist das Caching auf 1 festgelegt.

Wenn ich versuche, alle Daten manuell abzurufen, benötige ich 70.000 Auswahlen, was einen enormen Leistungsunterschied darstellt. Kennt jemand die internen Funktionen von Hibernate und wie man es manuell reproduziert?

31
iliaden

Ich habe die Lösung gefunden:

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

}
5
iliaden

Hier ist, was für mich funktioniert hat (spezifisch für Oracle, aber die Verwendung von scalar scheint der Schlüssel zu sein)

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

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

Dank den Plakaten hier: springsource_forum

24
Mike

Sie können die Hibernate Dialect-API für die Datenbankunabhängigkeit wie folgt verwenden

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

}
21
Punit Patel

Wenn Sie Oracle verwenden, sollten Sie die Cache-Größe für die Sequenz angeben. Wenn Sie routinemäßig Objekte in Stapeln von 5 KB erstellen, können Sie sie einfach auf 1000 oder 5000 festlegen. Wir haben dies für die für den Ersatzprimärschlüssel verwendete Sequenz durchgeführt und waren erstaunt, dass die Ausführungszeiten für einen ETL-Prozess in Java halbiert.

Ich konnte keinen formatierten Code in einen Kommentar einfügen. Hier ist die Sequenz DDL:

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

Um die neue ID zu erhalten, müssen Sie nur den Entitätsmanager flush ausführen. Siehe getNext() Methode unten:

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

Interessant, es funktioniert für Sie. Als ich Ihre Lösung ausprobiert habe, ist ein Fehler aufgetreten, der besagt, dass "Typenkonflikt: Konvertierung von SQLQuery in Query nicht möglich". -> Deshalb sieht meine Lösung so aus:

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

Mit dieser Lösung bin ich nicht auf Leistungsprobleme gestoßen.

Und vergessen Sie nicht, Ihren Wert zurückzusetzen, wenn Sie nur zu Informationszwecken wissen wollten.

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

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();
1
E L

Ihre Idee mit der gefälschten Entität SequenceGenerator ist gut.

@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 ist wichtig, den Parameter mit dem Schlüsselnamen "sequence_name" zu verwenden. Führen Sie in der Ruhezustandsklasse SequenceStyleGenerator eine Debugsitzung aus. Die Methode configure (...) in der Zeile final QualifiedName sequenceName = detectSequenceName (params, dialect, jdbcEnvironment); Hier erfahren Sie mehr darüber, wie der Sequenzname von Hibernate berechnet wird. Es gibt einige Standardeinstellungen, die Sie auch verwenden können.

Nach der gefälschten Entität habe ich ein CrudRepository erstellt:

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

In der Junit rufe ich die save-Methode des SequenceRepository auf.

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

Wenn es einen besseren Weg gibt, dies zu tun (vielleicht Unterstützung für einen Generator in einem beliebigen Feld anstelle von nur Id), würde ich ihn gerne anstelle dieses "Tricks" verwenden.

0
razvanone

So mache ich das:

@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);
0
John Tumminaro

Spring 5 hat einige eingebaute Hilfsklassen dafür: org/springframework/jdbc/support/incrementer

0
eztam