2011-06-17 19 views
22

Mam obiekt, który ma pole NON-ID, które należy ustawić z sekwencji. Obecnie pobieram pierwszą wartość sekwencji, przechowuję ją po stronie klienta i obliczam od tej wartości.pobranie następnej wartości sekwencji z bazy danych za pomocą hibernacji

Jednak szukam "lepszego" sposobu robienia tego. I wprowadziły sposób, aby pobrać następną wartość sekwencji:

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

Jednak ten sposób zmniejsza istotnie wydajność (tworzenie ~ 5000 obiektów zostaje spowolniony przez współczynnik 3 - od 5740ms do 13648ms).

Próbowałem dodać „fałszywy” podmiot:

@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 takie podejście nie działało (wszystkie identyfikatory były zwrócone 0).

Czy ktoś może mi doradzić, w jaki sposób wydajnie pobrać kolejną wartość sekwencji za pomocą Hibernacji?

Edit: Po dochodzeniu, I odkryli, że nazywając Query query = session.createSQLQuery("select nextval('mySequence')"); jest zdecydowanie mniej efektywne niż przy użyciu @GeneratedValue - z powodu Hibernate jakoś udaje się zmniejszyć liczbę pobiera podczas dostępu do sekwencji opisanej przez @GeneratedValue.

Na przykład, gdy tworzę 70 000 obiektów (w ten sposób z 70 000 kluczy podstawowych pobranych z tej samej sekwencji), otrzymuję wszystko, czego potrzebuję.

JEDNAK, Hibernate tylko wystawia select nextval ('local_key_sequence') poleceń. UWAGA: Po stronie bazy danych buforowanie jest ustawione na 1.

Jeśli spróbuję pobrać wszystkie dane ręcznie, zajmie mi to 70 000 elementów, a tym samym ogromną różnicę w wydajności. Czy ktokolwiek zna wewnętrzne funkcjonowanie Hibernate i jak go odtworzyć ręcznie?

Odpowiedz

5

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

} 
+2

Uwaga: ponieważ klasa wskazuje, że działa tylko na PostgreSQL, ponieważ składnia SQL jest odpowiednikiem Oracle. –

3

Jeśli korzystasz z Oracle, rozważ określenie 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 do zastępczego klucza podstawowego i byliśmy zdumieni, że czasy wykonania dla procesu ETL napisanego ręcznie w Javie spadły w połowie.

Nie mogę 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; 
+0

To brzmi interesująco. Czy możesz mi powiedzieć, jakie adnotacje użyłeś do ustawienia tworzenia partii (a tym samym pobierania partii z wartości sekwencji)? – iliaden

+0

@iliaden: Podałem rozmiar pamięci podręcznej w sekwencji DDL.Nie mogłem wkleić kodu do komentarza, więc zredagowałem odpowiedź. – Olaf

+0

@Olaf: 'cache 1000' wydaje się być polem wymaganym, ale jak mogę je odwzorować w Hibernate? – iliaden

2

Aby uzyskać nowy identyfikator, wszystko co musisz zrobić, to flush kierownik jednostki.Zobacz getNext() metoda 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(); 
    } 
} 
+1

to jest problem - sekwencja NIE jest kluczem podstawowym (NIE jest to pole ID). Chociaż będę badał użycie '' SequenceFetcher''' – iliaden

+1

SequenceFether było podejściem dodatkowym, ale hibernacja wymuszenie odwzorowuje je na tabelę [nieistniejącą]. – iliaden

+0

"em.flush()" dało mi błąd. Usunąłem go i działało dobrze. Dziękuję Ci! – roneo

22

Oto co pracował dla mnie (specyficzne dla Oracle, ale przy użyciu 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

+3

Jako notatkę, możesz także użyć StandardBasicTypes.LONG zamiast StandardBasicTypes.BIG_INTEGER ... – rogerdpack

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

Twoja teoria jest dobra i pomogła mi, ale twój SQL jest podatny na wstrzyknięcie. To nigdy nie jest dobre. – Makoto

16

można używać hibernacji dialekt API dla niezależności Database jak postępować

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

} 
+0

To jest piękne, z wyjątkiem tego, że – Marc

+2

+1 Myślę, że jest to jedyna odpowiedź na agnostyczną bazę danych, która jest mi potrzebna. (Mamy aplikację wykorzystującą Hibernate 4, która musi pobrać wartość inną niż id z sekwencji db, używając HSQLDB do testów i Oracle dla prawdziwej transakcji). Korzystając z próbnych zasobów java 7 możesz skrócić kod a kawałek. Musiałem użyć 'DatabaseMetaDataDialectResolutionInfoAdapter', aby zawinąć wynik' connection.getMetaData() 'przed przekazaniem go do' resolveDialect() '. @punitpatel dzięki! –

+0

Właściwie, muszę dodać, że używa to 'dialect.getSequenceNextValString()', które nie będzie działać dla baz danych nie obsługujących sekwencji ... –

0

Tu jest tak, jak to zrobić:

@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

Ciekawe, że działa dla Ciebie. Kiedy wypróbowałem twoje rozwiązanie, pojawił się błąd, mówiąc, że "Niezgodność typu: nie można przekonwertować z SQLQuery na Query". -> Dlatego moje rozwiązanie wygląda następująco:

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

Dzięki temu rozwiązaniu nie miałem problemów z wydajnością.

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

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

Twój pomysł z SequenceGenerator 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 "nazwa_przypisu". Uruchom sesję debugowania w klasie hibernacji SequenceStyleGenerator, metoda configure (...) w linii końcowej QualifiedName sequenceName = determineSequenceName (params, dialect, jdbcEnvironment); aby zobaczyć więcej informacji o tym, jak nazwa sekwencji jest obliczana przez Hibernate. Są tam pewne wartości domyślne, których możesz użyć.

Po fałszywym podmiotu, stworzyłem CrudRepository:

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

W JUnit wzywam metody save z SequenceRepository.

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

Jeśli jest lepszy sposób na zrobienie tego (może wsparcie dla generatora na dowolnym polu zamiast po prostu Id), byłbym bardziej niż szczęśliwy, gdyby go użył zamiast tego "triku".

Powiązane problemy