2012-10-16 21 views
7

Uruchamiamy aplikację internetową Spring 3.1/Hibernate 4/Java 7/Tomcat 7/MSSQL 2008 R2. Musimy radzić sobie ze starszymi danymi i zarchiwizowanymi danymi. Podczas wyodrębniania danych z archiwum potrzebujemy użyć oryginalnego unikalnego identyfikatora, aby inne (niezarchiwizowane) rekordy zostały poprawnie nawodnione. Te identyfikatory są przechowywane w polu klucz główny/auto increment.Hibernate 3.5 vs 4 IDENTITY_INSERT issues

Przed teraz, kiedy używaliśmy Wiosna 3.0/Hibernate 3.5, następujący kod pracował wstawić wyodrębniony zapis z powrotem do odpowiedniej tabeli (mamy już zmienne session, entity i fullTableName w zakresie):

session.doWork(new Work() 
{ 
    @Override 
    public void execute(Connection connection) throws SQLException 
    { 
     PreparedStatement statement = null; 
     try 
     { 
      statement = connection.prepareStatement(String.format("SET IDENTITY_INSERT %s ON", fullTableName)); 
      statement.execute(); 

      session.save(entity); 

      statement = connection.prepareStatement(String.format("SET IDENTITY_INSERT %s OFF", fullTableName)); 
      statement.execute(); 
     } 
     finally 
     { /* close the statement */ } 
    } 
}); 

Tak jak wspomniałem, wszystko działało poprawnie w Hibernate 3.5, ale po przejściu na Hibernate 4 przestało działać. Czy jest jakaś różnica między Work a IsolatedWork?

W celu rozwiązania tego problemu, i uniknąć wszelkich problemów interfejsu Praca, staraliśmy się, co następuje:

session.createSQLQuery(String.format("SET IDENTITY_INSERT %s ON", fullTableName)).executeUpdate(); 
session.save(entity); 
session.createSQLQuery(String.format("SET IDENTITY_INSERT %s OFF", fullTableName)).executeUpdate(); 

Jednak to nie działało. W szczególności wyjątek, który zostanie rzucony, to java.sql.SQLException: Cannot insert explicit value for identity column in table 'Employee' when IDENTITY_INSERT is set to OFF.. Jednak powinno być jasne, że przechodzimy przez bóle, aby go włączyć.

Zrobiliśmy ślad programu SQL Server Profiler i znaleźliśmy coś interesującego. W każdej z naszych ciał transakcji jest ustawiona opcja IMPLICIT_TRANSACTIONS. Oto niektóre przykładowe dane wyjściowe od śladu Profiler (mam zastąpić nasz rzeczywisty schemat z <schema>, a niektóre duże bitów danych o krótszych etykiet):

SET IMPLICIT_TRANSACTIONS ON 
go 
declare @p1 int 
set @p1=55 
exec sp_prepare @p1 output,N'',N'SET IDENTITY_INSERT <schema>.Employee ON',1 
select @p1 
go 
exec sp_execute 55 
go 

declare @p1 int 
set @p1=56 
exec sp_prepare @p1 output,N'<parameters for the INSERT>',N'insert into <schema>.Employee (<all the column names>) values (<all the parameters>)',1 
select @p1 
go 
exec sp_execute 56,<the actual values to insert> 
go 
IF @@TRANCOUNT > 0 ROLLBACK TRAN 
go 
IF @@TRANCOUNT > 0 COMMIT TRAN 
SET IMPLICIT_TRANSACTIONS OFF 
go 
exec sp_execute 54,N'Error writing EMPLOYEE archive record. ',<an id>,N'1' 
go 

Teraz jesteśmy specjalnie ustawienie IMPLICIT_TRANSACTIONS być wyłączony, poprzez połączenia .setAutoCommit (false) w naszej transakcji (transakcje są zarządzane przez Spring @ Transactional i Hibernate Transaction Manager). Oczywiście, to nie działa, ale jakie są alternatywy oprócz używania setAutoCommit i dlaczego miałoby działać w Spring3.0/Hibernate 3.5, ale nie Spring 3.1/Hibernate 4?

Dzięki za wszelkie przemyślenia lub sugestie - jesteśmy zaskoczeni.

Odpowiedz

2

Cóż, to było subtelne rozwiązanie ...

Nasza rozmowa Praca użył java.sql.PreparedStatement wewnętrznie, a my wtedy nazywa się metodą execute(). Najwyraźniej mówi to programowi SQL Server, aby opakował polecenie we własnej zapisanej procedurze, jak pokazują niektóre przykłady kodu.

Zmieniliśmy z użyciem PreparedStatement po prostu się java.sql.Statement i wywołanie metody execute():

session.doWork(new Work() 
{ 
    @Override 
    public void execute(Connection connection) throws SQLException 
    { 
     Statement statement = null; 
     try 
     { 
      statement = connection.createStatement(); 
      statement.execute(String.format("SET IDENTITY_INSERT %s ON", fullTableName)); 

      session.save(entity); 

      statement = connection.createStatement(); 
      statement.execute(String.format("SET IDENTITY_INSERT %s OFF", fullTableName)); 
     } 
     finally 
     { /* close the statement */ } 
    } 
}); 

Więc co za różnica? O ile można powiedzieć, PreparedStatement generuje wstępnie skompilowany SQL, podczas gdy Statement generuje statyczny SQL ... dokładnie to, czego potrzebowaliśmy do wywołania IDENTITY_INSERT!

Lekcja: jest wiele uli szumowin i podłości ... musimy być ostrożni!

+0

Dziękuję bardzo, na pewno zaoszczędziłem mi dużo czasu. –