The following comment has been added to this issue: Author: Gavin King Created: Wed, 22 Oct 2003 4:45 PM Body: I need something that is actually runnable. I don't understand this code at all. What on earth are all the useless-looking calls to saveOrUpdate() for?? The object is already associated with the session! --------------------------------------------------------------------- View the issue: http://opensource.atlassian.com/projects/hibernate/secure/ViewIssue.jspa?key=HB-420 Here is an overview of the issue: --------------------------------------------------------------------- Key: HB-420 Summary: Adding a new instance to a persistent bag and calling saveOrUpdate adds a duplicate instance to the bag but not the DB. Type: Bug Status: Unassigned Priority: Major Project: Hibernate2 Components: core Versions: 2.1 beta 3 2.1 beta 4 Assignee: Reporter: David Duffy Created: Wed, 22 Oct 2003 3:27 PM Updated: Wed, 22 Oct 2003 4:41 PM Environment: JDK1.4.2, Windows 2003 Server; JDK1.4.2, Windows 2000; JDK1.3.1 Windows 2000 Description: When a new (transient) instance of a persisted child is created and added to the "bag" of children on the parent and then the parent is re-persisted via Session.saveOrUpdate() a second instance of the child will be added to the "bag" but not to the database. However, calling the .size() method on the "bag" before calling saveOrUpdate() will cause the problem to disappear. A unit test class that demonstrates this is as follows: package com.fgl.ina.tests; import junit.framework.TestCase; import net.sf.hibernate.cfg.Configuration; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.Session; import net.sf.hibernate.LockMode; import java.net.URL; import java.util.Properties; import java.util.ArrayList; //import java.util.Map; import java.util.Iterator; import java.io.BufferedInputStream; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; import com.fgl.ina.stylecreation.Product; //import com.fgl.ina.stylecreation.details.ProductDescription; import com.fgl.ina.stylecreation.barcodes.ProductBarcode; import com.fgl.ina.stylecreation.lookups.BarcodeType; import com.fgl.ina.stylecreation.coloursize.ProductColourSizeHelper; import com.fgl.ina.stylecreation.coloursize.ProductColourAndSize; import com.fgl.ina.services.DataAccessService; import com.fgl.ina.mastertables.colours.Colour; import com.fgl.ina.mastertables.sizes.Size; /** * Tests Hibernate (sort of), or at least aspects that are of immediate concern at time of writing. * @author David Duffy */ public class HibernateTest extends TestCase { //TODO: make a test suite (or whatever it is) that will do the setup and teardown only once for all Hibernate tests private static final Log log = LogFactory.getLog(HibernateTest.class); private static final String TEST_CONFIG_PATH = "/com/fgl/ina/tests/hibernatetest.cfg.xml"; private static final String TEST_PROPERTIES_PATH = "/com/fgl/ina/tests/hibernatetest.properties"; private SessionFactory factory; public HibernateTest(String testName) { super(testName); } /** * Perform unit test set up before running test(s). */ protected void setUp() { Configuration configuration = null; URL configFileURL = null; try { configFileURL = Class.class.getResource(TEST_CONFIG_PATH); if (log.isDebugEnabled()) { log.debug("Initializing Hibernate from " + TEST_CONFIG_PATH + "..."); } BufferedInputStream bis = new BufferedInputStream(Class.class.getResourceAsStream(TEST_PROPERTIES_PATH)); Properties properties = new Properties(); properties.load(bis); bis.close(); configuration = (new Configuration().setProperties(properties)).configure(configFileURL); factory = configuration.buildSessionFactory(); } catch (Throwable t) { log.error("Exception while initializing Hibernate.", t); } } /** * Perform unit test tear down after running test(s). */ protected void tearDown() { try { if (factory != null) { factory.close(); } } catch (Throwable t) { log.error("Couldn't close session factory", t); } } // TODO: make setup/teardown do the supporting object creation when there is a suite that does the Hibernate config public void testAddingBarcodes() throws Exception { if (factory == null) { fail("SessionFactory == null"); } else { Session session = factory.openSession(); try { // create a new product, remove its descriptions temporarily and save it without them 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(); } } } } } --------------------------------------------------------------------- JIRA INFORMATION: This message is automatically generated by JIRA. If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa If you want more information on JIRA, or have a bug to report see: http://www.atlassian.com/software/jira |