Pregunta

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.

¿Fue útil?

Solución

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.

Otros consejos

You will either need to change your database's definition to auto-increment that id, and then tell hibernate to not assign the id itself and leave that to the database.

[callid] [int] NOT NULL IDENTITY(1, 1), % or (10000, 1) or use the highest id.

hibernate class:

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int callid;

Or go with something like what's documented here for the generator to look at the database:

    <id name="id" column="callid">
        <generator class="hilo">
          <param name="table">CallLog</param>
          <param name="column">callid</param>
        </generator>
    </id>        
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top