Link to home
Start Free TrialLog in
Avatar of aturetsky
aturetsky

asked on

hibernate saveOrUpdate

I have a PROGRAM table.  It's keyed by program_id generated by an oracle sequence.  It also has a program_name field which is not null and unique.

Here's how it's mapped:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
      <class name="smefsModel.pojo.Program" table="PROGRAM" schema="SMEFS">
            <id name="programId" column="PROGRAM_ID" type="long">
                  <generator class="sequence">
                        <param name="sequence">SMEFS_SEQ</param>
                  </generator>
            </id>
            <natural-id mutable="true">
                  <property name="programName" type="string" column="PROGRAM_NAME" length="30" unique="true" not-null="true" />
            </natural-id>
            <property name="programDesc" type="string" column="PROGRAM_DESC" length="100" />
      </class>
</hibernate-mapping>

So here the question: is there a built-in way to make sure that when I try to saveOrUpdate a program and it has the same program_name, it would automatically know to just update the existing one and not to generate a new sequence-based program_id.

So, for example, currently, when I do:
1      Program program1 = new Program("yo");
2      session.saveOrUpdate(program1);
3      Program program2 = new Program("yo");
4      program2.setProgramDesc("whatup");
5      session.saveOrUpdate(program2);

For line 5, I get:    
insert
    into
        SMEFS.PROGRAM
        (PROGRAM_NAME, PROGRAM_DESC, PROGRAM_ID)
    values
        (?, ?, ?)
10:45:50,866 DEBUG StringType:79 - binding 'yo' to parameter: 1
10:45:50,866 DEBUG StringType:71 - binding null to parameter: 2
10:45:50,866 DEBUG LongType:79 - binding '301028540' to parameter: 3
10:45:50,882  WARN JDBCExceptionReporter:71 - SQL Error: 1, SQLState: 23000
10:45:50,882 ERROR JDBCExceptionReporter:72 - ORA-00001: unique constraint (SMEFS.PROGRAM_UX1) violated

because it tries to insert a new record (and then, of course, it bombs since program_name is unique), instead of updating the existing one.  This happens because the key to the table is the sequence-based program_id rather than program_name.


Avatar of Nguyen Huu Phuoc
Nguyen Huu Phuoc
Flag of Viet Nam image

Hi aturetsky !
Please read here:
http://www.systemmobile.com/articles/IntroductionToHibernate.html#Creating%20and%20Updating%20Persistent%20Classes
Focus on the following lines:
"The saveOrUpdate(Object) call will save the object if the id property is null, issuing a SQL INSERT to the database. This refers to the unsaved-value attribute that we defined in the Player mapping document. If the id is not null, the saveOrUpdate(Object) call would issue an update, and a SQL UPDATE would be issued to the database. (Please refer to the sidebar Unsaved-Value Strategies for more information on this topic.)"
And here about "Unsaved Value Strategies"
The unsaved-value attribute supported by the id element indicates when an object is newly created and transient, versus an object already persisted. The default value is null, which should be sufficient for most cases. However, if your identifier property doesn't default to null, you should give the default value for a transient (newly created) object.Additional values supported by the unsaved-value attribute are: any, none and id-value.
Phuoc
Avatar of aturetsky
aturetsky

ASKER

I don't think unsaved-value is applicable here, since at the point of saveOrUpdate my instance is certainly newly created and transient.  It's just that when that newly created instance has a program_id key that matches one already in the database, hibernate is smart enough to issue an update rather than insert.  For example if I have a record with program_id 123 already in the DB and then do:

Program program = new Program();
program.setProgramId(new Long(123));
program.setProgramName("blah");
session.saveOrUpdate(program);

hibernate is smart enough to realize that the record with that program_id is already in DB and to issue an update and not to insert a new record with a new sequence-based program_id

but if I have a record with program_name "blah" already in the DB and then do:

Program program = new Program();
program.setProgramName("blah");
program.setProgramDesc("ssup");
session.saveOrUpdate(program);

hibernate doesn't realize that a record with program_name "blah" is already sitting in the database and that all that needs to be done is an update on a program_desc, but rather attempts to issue an insert with a new sequence-based program_id, resulting in a unique constraint violation error on the program_name field

So I am not sure how unsaved-value would help me here.

Hi aturetsky!
Quote:
but if I have a record with program_name "blah" already in the DB and then do:

Program program = new Program();
program.setProgramName("blah");
program.setProgramDesc("ssup");
session.saveOrUpdate(program);

hibernate doesn't realize that a record with program_name "blah" is already sitting in the database and that all that needs to be done is an update on a program_desc, but rather attempts to issue an insert with a new sequence-based program_id, resulting in a unique constraint violation error on the program_name field
My idea:
The objects is distinguished between each others by object identifier.
In your example, the record in your db with program_name "blah" is associated object with object identifier x because programe_name is not an object identifier.
When you do the following task:
Program program = new Program();
program.setProgramName("blah");
program.setProgramDesc("ssup");
session.saveOrUpdate(program);
your program object has an identifier whose value is y.
x<>y so they are two different objects so hibernate must be use "insert" instead of update.
Your table with program_name is unique why don't you use it as identifier of the objects?
Phuoc

 
Good DB design practices and hibernate authors recommend refraining from using a natural id as a key.  For one thing, despite its uniqueness, its value may be changed later on.  That's why I did not want to use program_name as key, and created a surrogate, sequential program_id key instead.

I realize it looks at the key to determine whether to insert or update.  I wish however, that using the natural-id element in hibernate mapping would not only tag it as unique and not null, but also have impact on the insert update behavior.
Hi aturetsky!
I think you should use merg for the first time and saveOrUpdate for another time
For first time:
program1 = merge(program1);
The other times:
Program program = new Program();
program.setProgramId(program1.getProgramId());
program.setProgramName("blah");
program.setProgramDesc("ssup");
session.saveOrUpdate(program);
Phuoc
well, in my case, that's the whole problem - the program doesn't know which time is first and which isn't until it looks to see if it's already in the database.  Now, of course, I can try to do a find by that program_name first and see if it's there - but that's exactly what I was trying to avoid.

Having said that, I am curious why you would recommend a merge rather than a find by program name.  What would be the goal of merge here?
ASKER CERTIFIED SOLUTION
Avatar of Nguyen Huu Phuoc
Nguyen Huu Phuoc
Flag of Viet Nam image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
note to the reader:
while the previous post is marked as accepted due to its general usefulness, please note that there does not appear to be a built-in way to drive insert-update behavior based on the value of non-key field, even if it's a candidate, natural key.