-->
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.  [ 24 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: How to deal with Bags using bidirectional Parent Child?
PostPosted: Wed Oct 15, 2003 5:10 pm 
Senior
Senior

Joined: Sun Aug 31, 2003 3:14 pm
Posts: 151
Location: Earth (at the moment)
I don't know if this is a bug or expected behaviour but I'm not sure what my approach should be to dealing with it.

I've got a collection on my parent class mapped as bag that holds bidirectionally associated children.

The weird thing is, when I create a new child, add it to the collection and call saveOrUpdate(parent) I end up with two instances of the child in the collection. I only added one of them.
I realize that the whole point of bag is to allow multiples of the same item but should a second copy be put into the bag when saveOrUpdate is called on the parent? Is this the expected desired behaviour?

I'm not having any problems with the association working or with the new children saving, I just end up with two copies of the child in the collection. I'm fairly certain this didn't occur a few days ago but I can't figure out what I could have changed that would have caused it to start? I download the latest Hibernate2 v21branch code from CVS every few days so is it possible there was a change made to the code that would change the behaviour?

Should my parent child relationship not be bidirectional? Or should I explicitly save the child and then refresh the parent?

Right now if I call refresh(parent) it doesn't "clean up" the collection, I still have two copies of the child, is that as it should be?

I could just reload the parent but somehow that seems like it should be unnecessary?

Thank-you.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Oct 15, 2003 5:30 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Is there only one association in the DB? Or are both captured into the DB?


Top
 Profile  
 
 Post subject: Only one row in DB
PostPosted: Wed Oct 15, 2003 6:02 pm 
Senior
Senior

Joined: Sun Aug 31, 2003 3:14 pm
Posts: 151
Location: Earth (at the moment)
There is only one row in the database.

And if I reload the parent object in a new session there will only be one instance of the child in the collection. If however I try to refresh, or even reload, the parent in the same session it was updated in I will of course still have two instances in the collection.

If I remove one of the duplicate children from the collection and call saveOrUpdate on the parent it will issue a delete to the database and delete the one row. If I then try to remove the remaining duplicate child instance and call saveOrUpdate it will again try to issue a delete which of course will generate an exception.

It seems to me that this portion of the handling is correct, when an object is removed from a collection delete it. But I don't think there should have been two instances in the collection in the first place correct?

Thank-you.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Oct 15, 2003 6:19 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Yeah, this should not be the way it works.

Could you post the mappings and the driver code?


Top
 Profile  
 
 Post subject: mappings et. al.
PostPosted: Wed Oct 15, 2003 6:57 pm 
Senior
Senior

Joined: Sun Aug 31, 2003 3:14 pm
Posts: 151
Location: Earth (at the moment)
The child:
Code:
   <class name="com.fgl.ina.stylecreation.barcodes.ProductBarcode" table="product_barcode">
      <id name="ID" column="barcode_ID" type="integer" unsaved-value="0">
         <generator class="net.sf.hibernate.id.IdentityGenerator"/>
      </id>
      <many-to-one name="parentColourSize">
         <column name="ina_prod_id" not-null="true"/>
         <column name="colour_ID"/>
         <column name="size_ID"/>
      </many-to-one>
      <property name="barcode" column="barcode_no" type="string"/>
      <many-to-one name="barcodeType" column="barcode_type_ID" outer-join="true"
            insert="true" update="true" cascade="none" not-null="true"/>
      <property name="primary" column="primary_ind" type="boolean" insert="true" update="true" not-null="true"/>
   </class>


The parent:
Code:
   <class name="com.fgl.ina.stylecreation.coloursize.ProductColourAndSize" table="product_colour_size">
      <composite-id>
         <key-many-to-one name="parentProduct" class="com.fgl.ina.stylecreation.Product" column="ina_prod_id"/>
         <key-many-to-one name="colour" class="com.fgl.ina.mastertables.colours.Colour"  column="colour_ID"/>
         <key-many-to-one name="size"   class="com.fgl.ina.mastertables.sizes.Size"      column="size_ID"/>
      </composite-id>
      <property name="available" column="avail_ind" type="boolean" not-null="true"/>
      <bag name="barcodes" cascade="all-delete-orphan" lazy="true" inverse="true" order-by="barcode_ID">
         <key>
            <column name="ina_prod_id" not-null="true" unique="false"/>
            <column name="colour_ID" not-null="true" unique="false"/>
            <column name="size_ID" not-null="true" unique="false"/>
         </key>
         <one-to-many class="com.fgl.ina.stylecreation.barcodes.ProductBarcode"/>
      </bag>
   </class>


My helper method for adding ProductBarcode instances to ProductColourAndSize instances (adding children to parent):
Code:
public class ProductColourAndSize implements Serializable {
   // ... abreviated ...
   private Collection barcodes;

   public boolean addBarcode(ProductBarcode barcode) {
      barcode.setParentColourSize(this);
      return barcodes.add(barcode);
   }
}


The code where this is used (abbreviated for brevity):
Code:
               ProductBarcode barcode = new ProductBarcode(barcodesForm.getBarcodeString(), colourSize, barcodeType);
                  colourSize.addBarcode(barcode);
                  boolean success = DataAccessService.saveOrUpdate(PersistanceFilter.getSession(), product);


And my method which wraps saveOrUpdate (redundantly):
Code:
   public static boolean saveOrUpdate(Session session, Object obj) throws Exception {
      boolean status = false;
      Transaction tx = null;

      try {
         tx = session.beginTransaction();
         session.saveOrUpdate(obj);
         tx.commit();
         status=true;
      } catch (Exception e) {
         tx.rollback();
         log.error("error saving or updating " +  obj.getClass().getName(), e);
      }

      return status;
   }


As for "driver code", do you mean the database driver or what I listed above? The database driver I'm using is Macromedia's SQL Server driver (which I suspect is the tds one...) because I don't have a license for j-netdirect yet.

Thank-you again.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Oct 15, 2003 8:13 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
This is just what I just meant driver code...

Does barcode.setParentColourSize() do anything like:
Code:
public void setParentColourSize(ProductColourAndSize colourSize)
{
    colourSize.getBarcodes().add(this);
    this.colourSize = colourSize;
}


Or does it just simply update the ProductColourAndSize association?

I have to take off, but I'll check in later tonight or first thing tomorrow morning.


Top
 Profile  
 
 Post subject: just the association
PostPosted: Wed Oct 15, 2003 8:39 pm 
Senior
Senior

Joined: Sun Aug 31, 2003 3:14 pm
Posts: 151
Location: Earth (at the moment)
It just sets the association, no extra collection add
Code:
   public void setParentColourSize(ProductColourAndSize parentColourSize) {
      this.parentColourSize = parentColourSize;
   }


Thank-you very much for your help in looking into this.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 16, 2003 9:26 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Could you do me another favor (in an attempt to not have to fire up the debugger)? Crank up logging on Hibernate and run that driver code again.

If possible, modify you code snippet
Code:
ProductBarcode barcode = new ProductBarcode(barcodesForm.getBarcodeString(), colourSize, barcodeType);
                  colourSize.addBarcode(barcode);
                  boolean success = DataAccessService.saveOrUpdate(PersistanceFilter.getSession(), product);


to look like:
Code:
ProductBarcode barcode = new ProductBarcode(barcodesForm.getBarcodeString(), colourSize, barcodeType);
                  colourSize.addBarcode(barcode);
                  logger.info("barcode count pre-persist [" + colourSize + "]");
                  boolean success = DataAccessService.saveOrUpdate
(PersistanceFilter.getSession(), product);
                  logger.info("barcode count post-persist [" + colourSize + "]");


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 16, 2003 11:24 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Oops, should have been:
Code:
ProductBarcode barcode = new ProductBarcode(barcodesForm.getBarcodeString(), colourSize, barcodeType);
                  colourSize.addBarcode(barcode);
                  logger.info("barcode count pre-persist [" + colourSize.getBarcodes().size() + "]");
                  boolean success = DataAccessService.saveOrUpdate
(PersistanceFilter.getSession(), product);
                  logger.info("barcode count post-persist [" + colourSize.getBarcodes().size() + "]");


Top
 Profile  
 
 Post subject: calling Collection.size() changes the behaviour
PostPosted: Thu Oct 16, 2003 7:04 pm 
Senior
Senior

Joined: Sun Aug 31, 2003 3:14 pm
Posts: 151
Location: Earth (at the moment)
Well, after several hours of testing I discovered the following occurs repeateably and predictably:

If I put the first debugging line in which you provided:
Code:
logger.info("barcode count pre-persist [" + colourSize.getBarcodes().size() + "]");

The problem goes away.

If I put the second debuggin line in:
Code:
logger.info("barcode count post-persist [" + colourSize.getBarcodes().size() + "]");

It has no effect on the results (with or without the first line).

Regardless of what level of debugging I have set, the presence or absence of that one debugging line before the call to saveOrUpdate changes the results. I infer from this that it is actually the call to the proxied <bag> Collection.size() which somehow causes things to straighten out and work properly (having said that I want to point out that the collection has already been initialized).

Here is an excert of the trace without the first debugging line, there are some interesting things happening that only happen when that line is absent:
Code:
16:05:11,329 DEBUG SessionImpl:3204 - initializing collection [com.fgl.ina.stylecreation.coloursize.ProductColourAndSize
.barcodes#com.fgl.ina.stylecreation.coloursize.ProductColourAndSize@13799e]
16:05:11,329 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
16:05:11,329 DEBUG BatcherImpl:226 - prepared statement get: select product_0_.barcode_ID as barcode_ID__, product_0_.in
a_prod_id as ina_prod2___, product_0_.colour_ID as colour_ID__, product_0_.size_ID as size_ID__, barcodet1_.barcode_type
_ID as barcode_type_ID0_, barcodet1_.barcode_type_code as barcode_2_0_, product_0_.barcode_ID as barcode_ID1_, product_0
_.ina_prod_id as ina_prod2_1_, product_0_.colour_ID as colour_ID1_, product_0_.size_ID as size_ID1_, product_0_.barcode_
no as barcode_no1_, product_0_.barcode_type_ID as barcode_6_1_, product_0_.primary_ind as primary_7_1_ from product_barc
ode product_0_ left outer join barcode_type barcodet1_ on product_0_.barcode_type_ID=barcodet1_.barcode_type_ID where pr
oduct_0_.ina_prod_id=? and product_0_.colour_ID=? and product_0_.size_ID=? order by product_0_.barcode_ID
Hibernate: select product_0_.barcode_ID as barcode_ID__, product_0_.ina_prod_id as ina_prod2___, product_0_.colour_ID as
colour_ID__, product_0_.size_ID as size_ID__, barcodet1_.barcode_type_ID as barcode_type_ID0_, barcodet1_.barcode_type_
code as barcode_2_0_, product_0_.barcode_ID as barcode_ID1_, product_0_.ina_prod_id as ina_prod2_1_, product_0_.colour_I
D as colour_ID1_, product_0_.size_ID as size_ID1_, product_0_.barcode_no as barcode_no1_, product_0_.barcode_type_ID as
barcode_6_1_, product_0_.primary_ind as primary_7_1_ from product_barcode product_0_ left outer join barcode_type barcod
et1_ on product_0_.barcode_type_ID=barcodet1_.barcode_type_ID where product_0_.ina_prod_id=? and product_0_.colour_ID=?
and product_0_.size_ID=? order by product_0_.barcode_ID
16:05:11,369 DEBUG BatcherImpl:232 - preparing statement
16:05:11,369 DEBUG IntegerType:45 - binding '303' to parameter: 1
16:05:11,369 DEBUG IntegerType:45 - binding '11' to parameter: 2
16:05:11,379 DEBUG IntegerType:45 - binding '3' to parameter: 3
16:05:11,389 DEBUG Loader:263 - result set contains (possibly empty) collection: [com.fgl.ina.stylecreation.coloursize.ProductColourAndSize.barcodes#com.fgl.ina.stylecreation.coloursize.ProductColourAndSize@13799e]
16:05:11,389 DEBUG SessionImpl:3039 - uninitialized collection: initializing
16:05:11,389 DEBUG Loader:174 - processing result set
...

Up until where the trace excert above starts the two traces (with and without the debugging line) are identical. However, without the call to size(), Hibernate proceeds to reinitialize the collection after having inserted the new child entry. With the call to size() it simply inserts the child and stops, no reinitializing.

The <bag> collection is mapped as lazy but by this point in time has already been initialized (and iterated through...).

I don't know how to do step through debugging in JRun4 so I'm not sure how much farther I can trace it.

Thank-you.

P.S. the FAQ says:
Quote:
I have some other kind of wierd bugs.....
Carefully check the accessor methods for your persistent properties. Broken get/set pairs are a common source of bugs. Remember Hibernate might call a getter method multiple times during a session.

Also, make sure that a call to an accessor method couldn't do anything wierd ... like initialize a lazy collection or proxy.

For some classes it can be worthwhile to provide two get/set pairs for certain properties - one pair for business code and one for Hibernate.


I don't have broken get/set pairs. I don't know that the accessor would/could cause initialization of the lazy collection but trying to use the collection would of course cause initialization if it were not already initialized (which it is).

Thank-you again.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 16, 2003 11:01 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
First, I totally missed that ProductBarcode was constructed with parameters. What is that constructor doing? Can you post that code?

Personally, I would strip this down to an extremely simplistic version of what is happen throughout this process into a very simplified main(). That way you can totally isolate this processing. Have this main:
1) open a session;
2) load the ProductColourAndSize instance you are playing with;
3) create a new ProductBarcode instance using that constructor;
4) add that ProductBarcode to the ProductColourAndSize using ProductColourAndSize.addBarCode();
5) start a txn;
6) call saveOrUpdate();
7) commit txn/ close session

What are the results from that?


Top
 Profile  
 
 Post subject: the constructor code
PostPosted: Fri Oct 17, 2003 12:09 pm 
Senior
Senior

Joined: Sun Aug 31, 2003 3:14 pm
Posts: 151
Location: Earth (at the moment)
The constructor:
Code:
   public ProductBarcode(String barcode, ProductColourAndSize parentColourSize, BarcodeType barcodeType) {
      this.barcode = barcode;
      this.parentColourSize = parentColourSize;
      this.barcodeType = barcodeType;
      this.primary = false;
   }

Although the setting of the parent is redundant in light of the fact that the helper method for adding it to the parent will set the parent that shouldn't cause a problem as it is just reassigning the same reference redundantly before anything else happens.

I will try to make a main method outside of the container that I can step through shortly.

Thank-you.


Top
 Profile  
 
 Post subject: too many lines of code to step through in one day...
PostPosted: Fri Oct 17, 2003 9:38 pm 
Senior
Senior

Joined: Sun Aug 31, 2003 3:14 pm
Posts: 151
Location: Earth (at the moment)
Okay, I made an isolated main (unit test rather) that I could step through. I duplicated the scenario I was dealing with. Created all new instances of my object hierarchy and persisted them et. al. to get to the same state as in my problem scenario and started stepping through the call to colourSize.getBarcodes().size() and the call to saveOrUpdate() and proceeded to make myself dizzy trying to follow everything that was going on with all the jumping around between classes. However, here is what I was able to understand:

colourSize.getBarcodes().size() calls ...hibernate.collection.Bag.size()
which in turn calls ...PersistentCollection.read()
which calls .initialize(false) which checks the initialized property which is false. And then proceeds to initialize the bag.

Why is initialized false?

The call to add the barcode instance goes into the bag proxy and "schedules an add"... huh?

Not being familiar with the inner workings of Hibernate code I got lost (and dizzy) and am not certain why the results are different or where the second instance gets added to the collection when the bag isn't "re-initailized" before calling saveOrUpdate.

My test code looks like so:
Code:
         Session session = factory.openSession();

         try {
            // create a new product, remove its descriptions temporarily and save it without them so I don't have to delete them later...
            Product product = new Product(0);
            product.getDetails().setVendorNumber(1);
            product.getDetails().setVpn("unittestvpn");
//            Map temp = product.getProductDescriptions();
            product.setProductDescriptions(null);
            DataAccessService.saveOrUpdate(session, product);


            // start building up the colours and sizes...
            try {
               // get a valid colour
               Colour colour = (Colour)DataAccessService.get(session, Colour.class).get(0);
               // get a valid size
               Size size = (Size)DataAccessService.get(session, Size.class).get(0);

               // add the valid colour and size (references) to the product and save the product (again).
               product.getColours().add(colour);
               product.getSizes().add(size);
               DataAccessService.saveOrUpdate(session, product);

               // create a Collection for colour/size combinations and run the helper method to create the records
               // for the existing colours and sizes on the product
               product.setColourSizes(new ArrayList());
               ProductColourSizeHelper.createColourSizesForColour(session, product, colour.getColourID());

               // save the product now that it has colour and size combinations (in this test there is only one).
               DataAccessService.saveOrUpdate(session, product);

               // use this session to load the barcode barcodeType and then close the session.
               BarcodeType barcodeType = (BarcodeType)DataAccessService.get(session, BarcodeType.class, new Integer(1));
               session.flush();   // redundant...
               session.close();

               // open a new session and reload the product so that null Collections will get proxies...
               session = factory.openSession();
               product = (Product)DataAccessService.get(session, Product.class, new Integer(product.getProductID()));
               session.close();

               // get a new session so that the product instance spans multiple sessions just like in the real
               // scenario being debugged...
               session = factory.openSession();
               // lock the product to the new session to reassociate it with the new session
               session.lock(product, LockMode.UPGRADE);

               // grab the colour and size combination, create a new barcode, and add it to the colour/size.
               ProductColourAndSize colourSize = (ProductColourAndSize)product.getColourSizes().iterator().next();
               ProductBarcode barcode = new ProductBarcode("498765123452", colourSize, barcodeType);
               colourSize.addBarcode(barcode);

               // the "magic" line that makes all the difference...
//               colourSize.getBarcodes().size();

               // save the product again...
               DataAccessService.saveOrUpdate(session, product);
               // test whether there is only one barcode in the collection (which there should be)
               assertTrue("barcodes.size() should == 1 but was actually " + colourSize.getBarcodes().size(),
                     colourSize.getBarcodes().size() == 1);
            } catch (Exception e) {
               log.fatal("?", e);
               fail("exception");
            } finally {
               if (product != null) {
                  try {
                     Iterator colourSizeIterator = product.getColourSizes().iterator();
                     while (colourSizeIterator.hasNext()) {
                        ProductColourAndSize deleteMe = (ProductColourAndSize)colourSizeIterator.next();
                        colourSizeIterator.remove();
                        DataAccessService.delete(session, deleteMe);
                     }
                     DataAccessService.saveOrUpdate(session, product);
                  } catch (Exception e) {
                     log.warn("couldn't remove colours/sizes and resave product", e);
                  }
                  try {
                     DataAccessService.delete(session, product);
                  } catch (Exception e2) {
                     log.error("cleanup failed: could not delete unittest product " + product.getProductID(), e2);
                  }
               }
            }
         } catch (Exception e) {
            log.error("?", e);
            fail("exception");
         } finally {
            if (session != null && session.isOpen()) {
               session.close();
            }
         }


Thank-you for your help. Please let me know if I've done something I'm not supposed to with the disconnected sessions?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Oct 17, 2003 10:24 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Quote:
The call to add the barcode instance goes into the bag proxy and "schedules an add"... huh?

That actually makes sense. When using bag/list, Hibernate does not need to initialize the collection on add. It needs to do that with Sets to be able to maintain the semantics of the Set contract (i.e., no dup instances).

Something else that this full example brought out. I notice that you use session.lock(product, LockMode.UPGRADE). I do not use an architecture where detached instances are necessary, so my knowledge of this bit is not as good as others. But my understanding was that detached instances needed to be reattched to the session through lock() by using LockMode.NONE. Basically, I did not think you could jump straight to write locks for detached instances. So you may want to play with that piece by changing to use LockMode.NONE. And actually, since just a couple of lines later you explictly call saveOrUpdate(), the lock piece is not really even needed.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Oct 19, 2003 7:31 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
If you do now have a very simple testcase, submit it to JIRA and I will take a quick look.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 24 posts ]  Go to page 1, 2  Next

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.