pobranie następnej wartości sekwencji z bazy danych za pomocą hibernate

Mam encję, która ma pole nie-ID, które musi być ustawione z sekwencji. Obecnie pobieram dla pierwszej wartości sekwencji, przechowuję ją po stronie klienta i obliczam z tej wartości.

Szukam jednak "lepszego" sposobu na zrobienie tego. Zaimplementowałem sposób pobierania następnej wartości sekwencji:
public Long getNextKey()
{
    Query query = session.createSQLQuery( "select nextval('mySequence')" );
    Long key = ((BigInteger) query.uniqueResult()).longValue();
    return key;
}

Jednak w ten sposób znacznie zmniejsza wydajność (tworzenie ~5000 obiektów zostaje spowolnione o współczynnik 3 - z 5740ms do 13648ms).

Próbowałem dodać" fałszywy " byt:

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

    public long getId() {
        return id;
    }
}

Jednak to podejście również nie zadziałało(wszystkie zwrócone identyfikatory były równe 0).

Czy ktoś może mi doradzić jak efektywnie pobrać następną wartość sekwencji używając Hibernate?

Edit: po zbadaniu odkryłem, że wywołanie Query query = session.createSQLQuery( "select nextval('mySequence')" ); jest o wiele bardziej nieefektywne niż użycie @GeneratedValue - z powodu Hibernate jakoś udaje się zmniejszyć liczbę pobrań podczas uzyskiwania dostępu do sekwencji opisane przez @GeneratedValue.

Na przykład, kiedy tworzę 70 000 encji, (a więc z 70 000 kluczy podstawowych pobranych z tej samej sekwencji), dostaję wszystko, czego potrzebuję.

Jednak , problemy z hibernacją tylko 1404 select nextval ('local_key_sequence') polecenia. UWAGA: Po Stronie bazy danych buforowanie jest ustawione na 1.

Jeśli spróbuję pobrać wszystkie dane ręcznie, zajmie mi to 70,000 selekcji, a więc ogromna różnica w wydajności. Czy ktoś zna wewnętrzne funkcjonowanie Hibernate i jak odtworzyć ręcznie?

Author: CDspace, 2011-06-17

10 answers

Możesz użyć Hibernate dialekt API dla niezależności bazy danych w następujący sposób

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

}
 27
Author: Punit Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-27 14:52:29

Oto, co dla mnie zadziałało (specyficzne dla Oracle, ale użycie scalar wydaje się być kluczem)

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

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

Dzięki plakatom tutaj: springsource_forum

 25
Author: Mike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-04 12:24:39

Znalazłem rozwiązanie:

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

}
 6
Author: iliaden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-20 13:15:02

Jeśli używasz Oracle, rozważ podanie rozmiaru pamięci podręcznej dla sekwencji. Jeśli rutynowo tworzysz obiekty w partiach 5K, możesz po prostu ustawić je na 1000 lub 5000. Zrobiliśmy to dla sekwencji używanej dla zastępczego klucza podstawowego i byliśmy zdumieni, że czas wykonania procesu ETL ręcznie napisanego w Javie spadł o połowę.

Nie mogłem wkleić sformatowanego kodu do komentarza. Oto Sekwencja DDL:

create sequence seq_mytable_sid 
minvalue 1 
maxvalue 999999999999999999999999999 
increment by 1 
start with 1 
cache 1000 
order  
nocycle;
 3
Author: Olaf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-17 14:20:00

Aby uzyskać nowy identyfikator, wystarczy flush menedżer encji. Patrz metoda getNext() poniżej:

@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
Author: Bohemian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-17 14:05:42

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
Author: E L,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-04 13:06:26

Ciekawe, że to działa. Kiedy próbowałem rozwiązania wystąpił błąd, mówiąc, że "Type mismatch: cannot convert from sqlquery to Query". -- >Dlatego moje rozwiązanie wygląda tak:

SQLQuery query = session.createSQLQuery("select nextval('SEQUENCE_NAME')");
Long nextValue = ((BigInteger)query.uniqueResult()).longValue();
Dzięki temu rozwiązaniu nie napotkałem problemów z wydajnością.

I nie zapomnij zresetować wartości, jeśli chcesz tylko wiedzieć w celach informacyjnych.

    --nextValue;
    query = session.createSQLQuery("select setval('SEQUENCE_NAME'," + nextValue + ")");
 1
Author: M46,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-04 08:04:40

Wiosna 5 ma do tego kilka klas pomocników: org / springframework / jdbc / support / incrementer

 1
Author: eztam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-16 15:44:44

Oto Jak to robię:

@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
Author: John Tumminaro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-10 18:52:11

Twój pomysł z SequenceGenerator fake entity jest dobry.

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

Ważne jest, aby użyć parametru o nazwie klucza "sequence_name". Uruchom sesję debugowania na sekwencestylegenerator klasy hibernate, configure(...) method at the line final QualifiedName sequenceName = determineSequenceName (params, dialect, jdbcEnvironment); to see more details about how the sequencename is computed by Hibernate. Istnieje kilka domyślnych tam można również użyj.

Po fałszywym bycie, stworzyłem CrudRepository:

public interface SequenceRepository extends CrudRepository<SequenceGenerator, Long> {}
W Junit wywołuję metodę save z SequenceRepository.

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

Jeśli istnieje lepszy sposób, aby to zrobić (może wsparcie dla generatora na dowolnym typie pola zamiast tylko Id), byłbym bardziej niż szczęśliwy, aby użyć go zamiast tej "sztuczki".

 0
Author: razvanone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-10 15:12:30