I am having issues with the new property-level optimistic-lock setting in conjunction with the class-level optimistic-lock="all" setting.
First, let me explain why I'm considering using the optimistic-lock="all" setting (as opposed to the optimistic-lock="version" setting) and why the property-level optimistic-lock setting looks appealing.
I have a legacy database which already has a "version" column in several tables. I'm writing a new application using Hibernate that will have to interact with the same database at the same time as the legacy application. The legacy application does optimistic locking in much the same way as Hibernate (with the <version> element). However, the database itself handles updating the value in this column whenever a record is inserted or updated. In fact, for SQL Server we use the 'rowversion' data type (equivalent to byte[8]), also known as 'timestamp', which is made precisely for this purpose.
If I use the <version> element to map this column to a property using Hibernate, then Hibernate will try to update this column, in which case SQL Server will give an error: "Cannot update a timestamp column". A similar error is given when trying to insert a value in this column. I know it sounds very strange, but it would be ideal if Hibernate allowed me to use the update="false" setting on my <version> element. I want it to be in the WHERE clause, but I don't want the column to be updated. After the update, the persistent instance will not have the correct rowversion value, but I think that's OK because I'm using session-per-application-transaction and the Session and any persistent objects will always be discarded after each update.
It seems my only alternative (aside from not using managed versioning) is to use optimistic-lock="all". As long as I use dynamic-update="true", my rowversion column will not be written to the database (as long as it hasn't changed). But, it will still be included in the WHERE clause check for optimistic locking. Indeed that seemed to work when I tried it. However, my only problem with this approach is that all columns are included in the WHERE clause. In some cases there are an awful lot of columns, so for efficiency I'd really rather not have them all included in each UPDATE operation.
So, when I saw the new property-level optimistic-lock attribute, it seemed perfect. I could turn off optimistic locking for all properties except my rowversion column. Unfortunately, it didn't quite work. I'm getting an "Invalid parameter binding(s)" error whenever I try to set optimistic-lock="false" for any properties if I have optimistic-lock="all" for the class. Shouldn't that work? It's generating the correct parameterized SQL query, but it's trying to put in too many parameters. Can that be fixed?
Hibernate version: 3.0 (31 March 2005)
Mapping documents: Employee.hbm.xml
Code:
<hibernate-mapping>
<class name="com.company.Employee" table="EMPLOYEES"
dynamic-insert="false" dynamic-update="true" optimistic-lock="all">
<id name="id" column="EmployeeID" type="long">
<generator class="native" />
</id>
<property name="firstName" column="FirstName" type="string" not-null="true" optimistic-lock="false" />
<property name="lastName" column="LastName" type="string" not-null="true" optimistic-lock="false" />
<property name="title" column="Title" type="string" not-null="false" optimistic-lock="false" />
<property name="version" column="version" type="binary" insert="false" update="true" optimistic-lock="true" />
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close(): Code:
Employee employee = new Employee();
session.load(employee, new Long((long)1));
employee.setLastName("Last" + System.currentTimeMillis()); // Dummy update for testing
session.saveOrUpdate(employee);
Full stack trace of any exception that occurs: Code:
org.hibernate.exception.SQLGrammarException: could not update: [com.company.Employee#1]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:59)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.persister.entity.BasicEntityPersister.update(BasicEntityPersister.java:2013)
at org.hibernate.persister.entity.BasicEntityPersister.updateOrInsert(BasicEntityPersister.java:1923)
at org.hibernate.persister.entity.BasicEntityPersister.update(BasicEntityPersister.java:2163)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:75)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:239)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:223)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:137)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:274)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:675)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:293)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:86)
at com.company.EmployeeTest.testSomething(EmployeeTest.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at junit.textui.TestRunner.doRun(TestRunner.java:116)
at com.intellij.rt.execution.junit2.IdeaJUnitAgent.doRun(IdeaJUnitAgent.java:57)
at junit.textui.TestRunner.start(TestRunner.java:172)
at com.intellij.rt.execution.junit.TextTestRunner2.startRunnerWithArgs(TextTestRunner2.java:23)
at com.intellij.rt.execution.junit2.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:97)
at com.intellij.rt.execution.junit2.JUnitStarter.main(JUnitStarter.java:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:78)
Caused by: java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC]Invalid parameter binding(s).
at com.microsoft.jdbc.base.BaseExceptions.createException(Unknown Source)
at com.microsoft.jdbc.base.BaseExceptions.getException(Unknown Source)
at com.microsoft.jdbc.base.BasePreparedStatement.validateParameterIndex(Unknown Source)
at com.microsoft.jdbc.base.BasePreparedStatement.setObjectInternal(Unknown Source)
at com.microsoft.jdbc.base.BasePreparedStatement.setString(Unknown Source)
at org.hibernate.type.StringType.set(StringType.java:24)
at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:62)
at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:44)
at org.hibernate.persister.entity.BasicEntityPersister.update(BasicEntityPersister.java:1988)
... 35 more
Name and version of the database you are using: Microsoft SQL Server 2000 (8.00.760 (SP3))
Microsoft SQL Server 2000 Driver for JDBC (Service Pack 3 (2.2.0040 from 7/6/2004))
The generated SQL (show_sql=true): Code:
Hibernate: select employee0_.EmployeeID as EmployeeID0_, employee0_.FirstName as FirstName0_0_, employee0_.LastName as LastName0_0_, employee0_.Title as Title0_0_, employee0_.version as version0_0_ from EMPLOYEES employee0_ where employee0_.EmployeeID=?
Hibernate: update EMPLOYEES set LastName=? where EmployeeID=? and version=?
Debug level Hibernate log excerpt:Code:
13:53:40,871 WARN JDBCExceptionReporter:57 - SQL Error: 0, SQLState: 07009
13:53:40,871 ERROR JDBCExceptionReporter:58 - [Microsoft][SQLServer 2000 Driver for JDBC]Invalid parameter binding(s).
13:53:40,871 ERROR AbstractFlushingEventListener:277 - Could not synchronize database state with session