-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 
Author Message
 Post subject: Database is inconsistent after saving the same object a 2nd
PostPosted: Tue Mar 13, 2007 10:18 am 
Newbie

Joined: Tue Mar 13, 2007 9:59 am
Posts: 6
Hibernate version: 3.2
mysql Ver 14.7

I have a many-to-many relation from a Plan to an array of Step.
These are my hibernate mappings:

Code:
<hibernate-mapping default-access="field">
    <class name="testHibernate.Plan" table="PLANS" >
        <id name="id">
            <generator class="increment" />
        </id>

        <list name="stepList" cascade="all" lazy="false">
            <key column="plan_id" foreign-key="id" />
            <list-index column="list_index"/>
            <many-to-many class="testHibernate.Step" column="step_id"/>
        </list>

    </class>

    <class name="testHibernate.Step" table="STEPS" >
        <id name="id">
            <generator class="increment"/>
        </id>

        <property name="parameterX" type="int"/>

    </class>
</hibernate-mapping>


Now, when I save the same plan (a plan with 2 steps) a 2nd time,
then I get these DB tables:
Code:
mysql> select * from PLANS;
+----+
| id |
+----+
|  1 |
|  2 |
+----+
2 rows in set (0.00 sec)

mysql> select * from STEPS;
+----+------------+
| id | parameterX |
+----+------------+
|  1 |         15 |
|  2 |         43 |
+----+------------+
2 rows in set (0.00 sec)

mysql> select * from stepList;
+---------+---------+------------+
| plan_id | step_id | list_index |
+---------+---------+------------+
|       2 |       2 |          1 |
|       2 |       1 |          0 |
+---------+---------+------------+
2 rows in set (0.00 sec)


The problem is, that the PLANS table still holds 2 plans, whereas
the stepList table deleted the references to the first plan. This
makes the database inconsistent.

I would expect a stepList table like this:
Code:
+---------+---------+------------+
| plan_id | step_id | list_index |
+---------+---------+------------+
|       1 |       2 |          1 |
|       1 |       1 |          0 |
|       2 |       2 |          1 |
|       2 |       1 |          0 |
+---------+---------+------------+  (this is a hand edited table!)


Can I change something in my mappings to get the desired behaviour?

(BTW: Please don't tell me, that I just should not write the same
object a 2nd time, because I am just a persistence provider, and
can not be sure, that the users of it never call it for saving with
an already saved object a 2nd time.)

Maybe I should give the sql log during the 2nd write as well:
Code:
Hibernate: insert into PLANS (id) values (?)
Hibernate: update STEPS set parameterX=? where id=?
Hibernate: update STEPS set parameterX=? where id=?
[b]Hibernate: delete from stepList where plan_id=?[/b]
Hibernate: insert into stepList (plan_id, list_index, step_id) values (?, ?, ?)
Hibernate: insert into stepList (plan_id, list_index, step_id) values (?, ?, ?)

It is this delete statement I don't want to have.

Thanks for your help in advance!

Tom[/b][/code]


Top
 Profile  
 
 Post subject: saveOrUpdate
PostPosted: Tue Mar 13, 2007 11:21 am 
Regular
Regular

Joined: Wed Aug 24, 2005 11:49 am
Posts: 63
Do you call save() with the already persisted Plan?
If so, I think the problem is that you insert a new Plan, but the collection of Steps is already attached to the first instance. Saving it 'moves' the whole set to the second Plan.

Anyway, the session api says you should call 'save()' with a transient instance. Your plan is not, so results are undefined I guess..

I would 'expect' two new steps (3 & 4) as well (since cascade is 'all' and you call save(), this should result in insert statements on the Step table).

_________________
Edwin van der Elst
Finalist IT Group


Top
 Profile  
 
 Post subject: Re: saveOrUpdate
PostPosted: Tue Mar 13, 2007 11:30 am 
Newbie

Joined: Tue Mar 13, 2007 9:59 am
Posts: 6
evdelst wrote:
Do you call save() with the already persisted Plan?


Yes. I actually closed the whole session before saving a 2nd time.

Code:
package testHibernate;

import java.util.*;
import org.hibernate.*;

public class TestHibernate {

   public static void main(String args[]) {

      Step step1 = new Step(15);
      Step step2 = new Step(43);
      
      Plan plan = new Plan();
      plan.addStep(step1);
      plan.addStep(step2);

      System.out.println("################ part 1 ");
      savePlan(plan);

      System.out.println("################ part 2 ");
      showDB();

      System.out.println("################ part 3 ");
      savePlan(plan);
      
      System.out.println("################ part 4 ");
      showDB();

      HibernateUtil.shutdown();
   }
   

   public static void savePlan (Plan plan) {
      try {
         Session session = HibernateUtil.getSessionFactory().openSession();
         Transaction tx = session.beginTransaction();
         session.save(plan);
         tx.commit();
         session.close();
      }
      catch (Exception ex) {
         ex.printStackTrace();
      }
   }

   
   public static void showDB () {
      Session session = HibernateUtil.getSessionFactory().openSession();
      Transaction tx = session.beginTransaction();

      List plans = session.createQuery("from Plan").list();
      System.out.println("######### DB content ##################");
      System.out.println(plans.size() + " plan(s) found:");

      for (Iterator iter = plans.iterator(); iter.hasNext();) {
         Plan planX = (Plan) iter.next();
         System.out.print("   Plan " + planX.getId() + ": ");
         System.out.println("num of steps: " + planX.getStepList().size());
      }
      System.out.println("########## end #################");

      tx.commit();
      session.close();
   }

}


I don't want to quote too much here, but everbody is welcome to download
the whole test example from http://www.tschuett.de/testHibernate.tar
(It is not much, just 20k)

Tom


Top
 Profile  
 
 Post subject: Doesn't matter
PostPosted: Tue Mar 13, 2007 11:35 am 
Regular
Regular

Joined: Wed Aug 24, 2005 11:49 am
Posts: 63
Closing the session makes the Plan detached.
It is still not transient.

Only a new object (new Plan()) or a deleted object is transient.

_________________
Edwin van der Elst
Finalist IT Group


Top
 Profile  
 
 Post subject: Re: saveOrUpdate
PostPosted: Tue Mar 13, 2007 11:39 am 
Newbie

Joined: Tue Mar 13, 2007 9:59 am
Posts: 6
evdelst wrote:
If so, I think the problem is that you insert a new Plan, but the collection of Steps is already attached to the first instance. Saving it 'moves' the whole set to the second Plan.

Yes, this is what happens (its the "update STEPS " in the sql log).

evdelst wrote:
Anyway, the session api says you should call 'save()' with a transient instance. Your plan is not, so results are undefined I guess..


Uhh - where exactly is it saying this important issue?

evdelst wrote:
I would 'expect' two new steps (3 & 4) as well (since cascade is 'all' and you call save(), this should result in insert statements on the Step table).


No, they are just updated.

Tom


Top
 Profile  
 
 Post subject: Re: saveOrUpdate
PostPosted: Tue Mar 13, 2007 11:45 am 
Regular
Regular

Joined: Wed Aug 24, 2005 11:49 am
Posts: 63
tom4321 wrote:
evdelst wrote:


evdelst wrote:
Anyway, the session api says you should call 'save()' with a transient instance. Your plan is not, so results are undefined I guess..


Uhh - where exactly is it saying this important issue?

Tom


In the api docs for Session:
Hibernate API documentation wrote:
The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes. Instances may exist in one of three states:

transient: never persistent, not associated with any Session
persistent: associated with a unique Session
detached: previously persistent, not associated with any Session

Transient instances may be made persistent by calling save(), persist() or saveOrUpdate(). Persistent instances may be made transient by calling delete(). Any instance returned by a get() or load() method is persistent. Detached instances may be made persistent by calling update(), saveOrUpdate(), lock() or replicate(). The state of a transient or detached instance may also be made persistent as a new persistent instance by calling merge().

_________________
Edwin van der Elst
Finalist IT Group


Top
 Profile  
 
 Post subject: Re: saveOrUpdate
PostPosted: Tue Mar 13, 2007 12:23 pm 
Newbie

Joined: Tue Mar 13, 2007 9:59 am
Posts: 6
evdelst wrote:
Anyway, the session api says you should call 'save()' with a transient instance. Your plan is not, so results are undefined I guess..


Yes, this must really be the point, you are right.
But how to handle this now?

I changed my code now, that a new plan is created with
private int id = -1;
(and there is no setter), and added a testing of the id to my
savePlan method.

Code:
   public static void savePlan (Plan plan) {

      ClassMetadata md = HibernateUtil.getSessionFactory().getClassMetadata(Plan.class);
      Serializable id = md.getIdentifier(plan, EntityMode.POJO);
      //System.out.println("### Saving plan with current id: " + id);
      if (! id.toString().equalsIgnoreCase("-1")) {
         System.out.println("Won't save same object again.");
         return;
      }
      
      
      try {
         Session session = HibernateUtil.getSessionFactory().openSession();
         Transaction tx = session.beginTransaction();
         session.save(plan);
         tx.commit();
         session.close();
      }
      catch (Exception ex) {
         ex.printStackTrace();
      }
   }


Is this an encoraged way of dealing with this problem, or is there a better solution?

Tom

(BTW: As there might be no follow up: Thanks very much, this clarifying was very helpful!)


Top
 Profile  
 
 Post subject: Possible solutions
PostPosted: Tue Mar 13, 2007 12:39 pm 
Regular
Regular

Joined: Wed Aug 24, 2005 11:49 am
Posts: 63
Before I go home :-)

If you use saveOrUpdate() the second call so save doesn't insert, but updates (nothing happens in this case).

If you want a new Plan, you need to create a new instance and copy the steps to a new collection of steps in the new plan instance (don't copy the reference to the collection, it contains a reference to the 'owning' Plan, Hibernate gets confused if you do this) .

However, if you also want a new set of Steps, you need to create new instances of Step as well.

(This is called a 'deep copy', sometimes implemented by creating a constructor that takes 'Plan' as an argument)

Btw: if you use Integer as a type for your primary key (instead of int) you can check for 'null'.

_________________
Edwin van der Elst
Finalist IT Group


Top
 Profile  
 
 Post subject: Re: Possible solutions
PostPosted: Tue Mar 13, 2007 1:14 pm 
Newbie

Joined: Tue Mar 13, 2007 9:59 am
Posts: 6
evdelst wrote:
Before I go home :-)

... time for me to find a job again, so getting paid for all this :-)

evdelst wrote:
If you use saveOrUpdate() the second call so save doesn't insert, but updates (nothing happens in this case).


That's funny: In the reference I just found this:

10.7. Automatic state detection

Hibernate users have requested a general purpose method that either saves a transient instance by generating a new identifier or updates/reattaches the detached instances associated with its current identifier. The saveOrUpdate() method implements this functionality.

--> so my concern was justyfied, and moreover, I found just the right time for starting with hibernate ;-)

BTW: In "21.4. Cascades and unsaved-value" I found:
Hibernate will use the identifier [ and ...] to determine which of the children are new.
So my approach wasn't too bad...

Tom


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.