In setting up some unit tests for my DAO, I came across strange behavior in Session#merge(). It appears that merging a detached object which has a non-existent primary key (where normally the keys are "natively" generated) causes the row to be inserted, ignoring the bogus primary key. Has anyone else noticed this behavior?
This is undesirable since it could cause rows to be inserted where an update was intended. This could happen if the target row was concurrently deleted in another session, or if the id in the detached object being merged was somehow corrupted.
Hibernate version: 3.0.5
Mapping documents:
<hibernate-mapping package="testutils.widget">
<class name="Widget" table="WIDGETS" lazy="false">
<id name="id" type="java.lang.Long" column="WIDGET_ID">
<generator class="native"/>
</id>
<property name="name">
<column name="WIDGET_NAME" not-null="true" unique="true"/>
</property>
<property name="description">
<column name="WIDGET_DESC"/>
</property>
</class>
Code between sessionFactory.openSession() and session.close():
String w1Name = "foo";
String w1Desc = "a test widget";
Widget w1 = new Widget(w1Name,w1Desc);
w1.setId(2718281L); // setting nonexistent primary key for test
Transaction tx = session.beginTransaction();
session.merge(w1); // row is inserted but with generated id
tx.commit();
Name and version of the database you are using: HSQLDB 1.0ea4
The generated SQL (show_sql=true):
Hibernate: select widget0_.WIDGET_ID as WIDGET1_0_, widget0_.WIDGET_NAME as WIDGET2_0_0_, widget0_.WIDGET_DESC as WIDGET3_0_0_ from WIDGETS widget0_ where widget0_.WIDGET_ID=?
Hibernate: insert into WIDGETS (WIDGET_NAME, WIDGET_DESC, WIDGET_ID) values (?, ?, null)
Hibernate: call identity()
Debug level Hibernate log excerpt:
opened session at timestamp: 4613822072254464
begin
opening JDBC connection
current autocommit status: true
disabling autocommit
id unsaved-value: null
detached instance of: testutils.widget.Widget
merging detached instance
loading entity: [testutils.widget.Widget#2718281]
attempting to resolve: [testutils.widget.Widget#2718281]
object not resolved in any cache: [testutils.widget.Widget#2718281]
Materializing entity: [testutils.widget.Widget#2718281]
loading entity: [testutils.widget.Widget#2718281]
about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
select widget0_.WIDGET_ID as WIDGET1_0_, widget0_.WIDGET_NAME as WIDGET2_0_0_, widget0_.WIDGET_DESC as WIDGET3_0_0_ from WIDGETS widget0_ where widget0_.WIDGET_ID=?
Hibernate: select widget0_.WIDGET_ID as WIDGET1_0_, widget0_.WIDGET_NAME as WIDGET2_0_0_, widget0_.WIDGET_DESC as WIDGET3_0_0_ from WIDGETS widget0_ where widget0_.WIDGET_ID=?
preparing statement
binding '2718281' to parameter: 1
about to open ResultSet (open ResultSets: 0, globally: 0)
processing result set
done processing result set (0 rows)
about to close ResultSet (open ResultSets: 1, globally: 1)
about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
closing statement
total objects hydrated: 0
initializing non-lazy collections
done entity load
merging transient instance
saving [testutils.widget.Widget#<null>]
executing insertions
Inserting entity: testutils.widget.Widget (native id)
about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
insert into WIDGETS (WIDGET_NAME, WIDGET_DESC, WIDGET_ID) values (?, ?, null)
Hibernate: insert into WIDGETS (WIDGET_NAME, WIDGET_DESC, WIDGET_ID) values (?, ?, null)
preparing statement
Dehydrating entity: [testutils.widget.Widget#<null>]
binding 'foo' to parameter: 1
binding 'a test widget' to parameter: 2
about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
closing statement
about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
call identity()
Hibernate: call identity()
preparing statement
Natively generated identity: 1
about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
closing statement
commit
automatically flushing session
flushing session
processing flush-time cascades
dirty checking collections
Flushing entities and processing referenced collections
Processing unreferenced collections
Scheduling collection removes/(re)creates/updates
Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
listing entities:
testutils.widget.Widget{description=a test widget, name=foo, id=1}
executing flush
post flush
before transaction completion
before transaction completion
re-enabling autocommit
committed JDBC Connection
after transaction completion
after transaction completion
closing session
closing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
after transaction completion
after transaction completion
|