Search code examples
hibernategwtsql-server-2000gilead

Setting Hibernate to get the next key by looking at the database


I am developing a web based application to replace a desktop based one. I need to have them both work on the same database. For the web-based application I am using GWT and Hiberate(with Gilead), running on Tomcat 7.0. The SQL server is MSSQL 2000.

I am getting the exception:

com.microsoft.sqlserver.jdbc.SQLServerException: Violation of PRIMARY KEY constraint 'PK_CallLog'. Cannot insert duplicate key in object 'CallLog'.

To get the exception I do the following steps:

  1. Add a call record with the old application
  2. Add a call record with the new application (using hibernate).

It seems that hibernate is using its own cache and not looking at the database to figure out what the next primary key should be.
Is there a way to force hibernate to get the next key by looking at the database?

This is the mapping for the call record:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hib.....dtd">

<hibernate-mapping>
    <class name="com.asi.shared.Call" table="CallLog">
        <id name="id" column="callid">
            <generator class="increment"/>
        </id>        
        <property name="caller"/>
        <property name="callDate" column="calldate"/>
        .... other props ....
        <property name="checkOut" column="checkout"/>
        <many-to-one name="customer" class="com.asi.shared.Customer"
                     column="customerid" not-found="ignore"/>
    </class>
</hibernate-mapping>

This is the method I am using to add a new call:

public Integer saveCall(Call call){
    DebugLog.print("HelpDeskServiceImpl.saveCall(call)");
    Session session = gileadHibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();
    check(session);
    session.saveOrUpdate(call);
    session.getTransaction().commit();
    return call.getId();
}

Schema for the call log:

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[CallLog]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[CallLog]
GO

CREATE TABLE [dbo].[CallLog] (
    [callid] [int] NOT NULL ,
    [caller] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [calldate] [datetime] NULL ,
    .... other columns ....
    [checkout] [varchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NULL 
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

I would like to avoid changing the database as much as possible, I don't want to break the older application.


Solution

  • Yes, increment generator uses internal cache and is not intended to be used when multiple processes can access a database simultaneosly.

    Assuming that you cannot change the database and the legacy application, and that legacy application uses increment-like id generation strategy too, the only possible solution would be to use an improved increment-like strategy.

    There are two possible options:

    • Issue a query to determine max id value manually before saving each Call and assign identifier manually

    • Take a look at the IncrementGenerator and implement the similar one without caching the value

    Note that these strategies are still vulnerable to possible id collision if multiple transactions save Call at exactly the same moment.

    If you can change the database and the legacy application, look at the dlamblin's suggestion.