Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate 3.5 vs 4 IDENTITY_INSERT issues

We run a Spring 3.1/Hibernate 4/Java 7/Tomcat 7/MSSQL 2008 R2 web application. We must deal with legacy data and archived data. When extracting data from an archive, we have a need to use the original unique identifier so that other (non-archived) records will re-hydrate correctly. These identifiers are stored in the primary key/auto increment field.

Prior to now, when we were using Spring 3.0/Hibernate 3.5, the following code worked to INSERT an extracted record back into its appropriate table (we already have the variables session, entity, and fullTableName in scope):

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 */ }
    }
});

Like I mentioned, this all worked fine in Hibernate 3.5, but now that we've upgraded to Hibernate 4, it has stopped working. Is there something up with the difference between Work and IsolatedWork?

In an effort to solve the problem, and avoid any Work interface issues, we tried the following:

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

However, this did not work either. Specifically, the exception that gets thrown is java.sql.SQLException: Cannot insert explicit value for identity column in table 'Employee' when IDENTITY_INSERT is set to OFF. Yet, it should be clear that we're going through pains to set it ON.

We did a SQL Server Profiler trace of the situation, and found something interesting. Something is setting IMPLICIT_TRANSACTIONS ON in each of our transaction bodies. Here's some sample output from the Profiler trace (I've replaced our actual schema with <schema>, and some large bits of data with shorter labels):

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

Now, we are specifically setting IMPLICIT_TRANSACTIONS to be OFF, via Connection.setAutoCommit(false) in our transaction (transactions are being managed via Spring @Transactional and Hibernate Transaction Manager). Obviously, that is not working, but what are the alternatives apart from using setAutoCommit, and why would it work in Spring3.0/Hibernate 3.5 but not Spring 3.1/Hibernate 4?

Thanks for any thoughts or suggestions - we're stumped.

like image 960
OrangeWombat Avatar asked Oct 16 '12 18:10

OrangeWombat


1 Answers

Well, it was a subtle solution...

Our Work call used a java.sql.PreparedStatement internally, and we then called the execute() method. Apparently, this tells SQL Server to wrap the command in its own stored procedure, as some of the code samples show.

We changed from using a PreparedStatement to simply a java.sql.Statement and calling its execute() method:

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 */ }
    }
});

So, what's the difference? As far as we can tell, the PreparedStatement generates pre-compiled SQL, whereas the Statement generates static SQL... exactly what we needed for the call to IDENTITY_INSERT!

Lesson: there are many hives of scum and villainy... we must be cautious!

like image 189
OrangeWombat Avatar answered Nov 03 '22 10:11

OrangeWombat