Henno -- Try changing :
firstSaved.getProducts().addAll(0, Arrays.asList(banana, pear)); to firstSaved.getProducts().addAll(Arrays.asList(banana, pear)); In my tests it seemed to make the problem go away... we're somehow getting confused when writing the join table on the second update. Please go ahead and open a JIRA for this issue. Thanks, Rick On Mon, Feb 21, 2011 at 1:47 PM, Rick Curtis <[email protected]> wrote: > How are you enhancing your Entities? > > Thanks, > Rick > > > On Mon, Feb 21, 2011 at 11:22 AM, Henno Vermeulen > <[email protected]>wrote: > >> Although this workaround works in my toy example, it does not seem to work >> in our application, switching to GenerationType.TABLE does. >> >> Henno >> >> -----Oorspronkelijk bericht----- >> Van: Henno Vermeulen [mailto:[email protected]] >> Verzonden: maandag 21 februari 2011 17:50 >> Aan: '[email protected]' >> Onderwerp: RE: adding new entities to an existing List fails when using >> GenerationType.IDENTITY >> >> I found a workaround: >> >> In my test I directly added the new ProductOrderLines to the List in the >> ProductOrder. I do not know the implementation of this List because it is >> the one returned by OpenJPA (at least after detaching). >> When I set the list reference to a new arraylist and add both the existing >> elements and the new ones to this list, the test passes. >> I.e. change the line >> >> firstSaved.getProducts().addAll(0, Arrays.asList(banana, pear)); >> >> to >> >> List<ProductOrderLine> newProducts = new >> ArrayList<ProductOrderLine>(); >> firstSaved.getProducts().addAll(Arrays.asList(banana, >> pear)); >> newProducts.addAll(firstSaved.getProducts()); >> firstSaved.setProducts(newProducts); >> >> Makes the test work. >> >> Regards, Henno >> >> >> -----Oorspronkelijk bericht----- >> Van: Henno Vermeulen [mailto:[email protected]] >> Verzonden: maandag 21 februari 2011 17:19 >> Aan: '[email protected]' >> Onderwerp: adding new entities to an existing List fails when using >> GenerationType.IDENTITY >> >> Hello, >> >> I think I found a serious issue (in both OpenJPA 2.0.0 and 2.1.0) when I >> try to add new entities to an existing list and I use a generated identity >> with GenerationType.IDENTITY. >> >> I start with a fresh database and let OpenJPA create the schema. I have a >> ProductOrder entity that contains a List of ProductOrderLines, annotated as >> such: >> >> @Entity >> public class ProductOrder { >> ... >> @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) >> private List<ProductOrderLine> products = new >> ArrayList<ProductOrderLine>(); >> ... >> } >> >> The entity in the List (ProductOrderLine) has a generated id with >> GenerationType.IDENTITY. >> >> @Entity >> public class ProductOrderLine { >> >> @Id >> @GeneratedValue(strategy = GenerationType.IDENTITY) >> private Long id; >> ... >> } >> >> I start with a ProductOrder that has these products: >> 1 - orange >> 2 - apple >> >> I insert two new products into the list so that I get: >> null - banana >> null - pear >> 1 - orange >> 2 - apple >> >> Then I merge the entity (I work with attach/detach, not sure if this >> matters). >> OpenJPA merge correctly returns a ProductOrder with this list: >> >> 3 - banana >> 4 - pear >> 1 - orange >> 2 - apple >> >> However OpenJPA generates the wrong SQL so that the database contains >> something completely different and indeed selecting the ProductOrder by it's >> id gives: >> >> 3 - banana >> 4 - pear >> 4 - pear >> 4 - pear >> >> I tested this with sql server. (I tried hsqldb but this also suffers from >> bug https://issues.apache.org/jira/browse/OPENJPA-1066). >> This problem does not occur when I use GenerationType.AUTO for >> ProductOrderLine. My example uses a join table, but foreign key columns seem >> to have the same problem. Should I use another generation type? If so which >> one, and is it compatible with existing sql server data that had id values >> automatically generated by sql server? >> >> Please let me know if I should file a bug report and if I should attempt >> to convert my unit test to one accepted by OpenJPA standards. >> >> Regards, >> Henno Vermeulen >> >> >> package entities; >> >> import javax.persistence.Entity; >> import javax.persistence.GeneratedValue; >> import javax.persistence.GenerationType; >> import javax.persistence.Id; >> >> @Entity >> public class ProductOrderLine { >> >> @Id >> @GeneratedValue(strategy = GenerationType.IDENTITY) >> private Long id; >> >> private String name; >> >> public ProductOrderLine() { >> } >> >> public ProductOrderLine(String name) { >> this.name = name; >> } >> >> public String getName() { >> return name; >> } >> >> public void setName(String name) { >> this.name = name; >> } >> >> public Long getId() { >> return id; >> } >> >> } >> >> >> package entities; >> >> import java.util.ArrayList; >> import java.util.List; >> >> import javax.persistence.CascadeType; >> import javax.persistence.Entity; >> import javax.persistence.FetchType; >> import javax.persistence.GeneratedValue; >> import javax.persistence.GenerationType; >> import javax.persistence.Id; >> import javax.persistence.OneToMany; >> >> @Entity >> public class ProductOrder { >> >> @Id >> @GeneratedValue(strategy = GenerationType.IDENTITY) >> private Long id; >> >> // Workaround for >> https://issues.apache.org/jira/browse/OPENJPA-1947 >> private String name; >> >> @OneToMany(cascade = CascadeType.ALL, fetch = >> FetchType.EAGER) >> private List<ProductOrderLine> products = new >> ArrayList<ProductOrderLine>(); >> >> public ProductOrder() { >> } >> >> public Long getId() { >> return id; >> } >> >> public void setProducts(List<ProductOrderLine> products) { >> this.products = products; >> } >> >> public List<ProductOrderLine> getProducts() { >> return products; >> } >> >> } >> >> import java.util.ArrayList; >> import java.util.Arrays; >> import java.util.Collections; >> import java.util.Comparator; >> import java.util.List; >> >> import javax.persistence.EntityManager; >> import javax.persistence.EntityManagerFactory; >> import javax.persistence.Persistence; >> >> import junit.framework.TestCase; >> import entities.ProductOrder; >> import entities.ProductOrderLine; >> >> /** >> * I tested this with MSSQL SERVER (I tried hsqldb but this suffers from an >> * additional problem where it mixes up id 0 with id null, see >> * https://issues.apache.org/jira/browse/OPENJPA-1066)/ >> * >> * @author Henno Vermeulen >> */ >> public class ProductOrderTest extends TestCase { >> >> private EntityManagerFactory factory; >> >> public void setUp() { >> factory = >> Persistence.createEntityManagerFactory("testPU", System >> >> .getProperties()); >> } >> >> public void testProductOrderLineCopy() { >> // If we do this, it fails after the first >> save!!!! >> // hsqldb seems to convert null to 0 and >> confuse this with an existing >> // id or something >> // insertSomeData(); >> >> ProductOrder original = new ProductOrder(); >> ArrayList<ProductOrderLine> products = new >> ArrayList<ProductOrderLine>(); >> products.addAll(Arrays.asList(new >> ProductOrderLine("orange"), >> new >> ProductOrderLine("apple"))); >> original.setProducts(products); >> >> // START >> // null - orange >> // null - apple >> printProducts("Before first save:", >> original); >> ProductOrder firstSaved = save(original); >> // 1 - orange >> // 2 - apple >> printProducts("After first save:", >> firstSaved); >> >> ProductOrderLine orange = >> firstSaved.getProducts().get(0); >> ProductOrderLine apple = >> firstSaved.getProducts().get(1); >> assertEquals("orange", orange.getName()); >> assertEquals("apple", apple.getName()); >> Long orangeId = orange.getId(); >> Long appleId = apple.getId(); >> assertTrue(orangeId != null && appleId != >> null); >> >> ProductOrderLine banana = new >> ProductOrderLine("banana"); >> ProductOrderLine pear = new >> ProductOrderLine("pear"); >> assertTrue(banana.getId() == null && >> pear.getId() == null); >> >> firstSaved.getProducts().addAll(0, >> Arrays.asList(banana, pear)); >> // Changed to >> // null - banana >> // null - pear >> // 1 - orange >> // 2 - apple >> printProducts("Before second save:", >> firstSaved); >> assertExpectedProductLines(firstSaved, >> orangeId, appleId, true); >> >> ProductOrder secondSaved = save(firstSaved); >> // Expected after save: >> // 3 - banana >> // 4 - pear >> // 1 - orange >> // 2 - apple >> >> // On SQL server this is actually returned >> by save, but not by find! >> printProducts("After second save:", >> secondSaved); >> assertExpectedProductLines(secondSaved, >> orangeId, appleId, false); >> >> ProductOrder found = >> findById(secondSaved.getId()); >> printProducts("Found by id after second >> save:", found); >> // This one fails!!!! >> // On SQL server this returns >> // 3 - banana >> // 4 - pear >> // 4 - pear >> // 4 - pear >> assertExpectedProductLines(found, orangeId, >> appleId, false); >> } >> >> private void printProducts(String description, ProductOrder >> order) { >> System.out.println(description); >> for (ProductOrderLine p : >> order.getProducts()) { >> System.out.println(p.getId() >> + " - " + p.getName()); >> } >> } >> >> private void assertExpectedProductLines(ProductOrder line, >> Long orangeId, >> Long appleId, boolean >> beforeMerge) { >> List<ProductOrderLine> products = new >> ArrayList<ProductOrderLine>(); >> products.addAll(line.getProducts()); >> Collections.sort(products, new >> Comparator<ProductOrderLine>() { >> @Override >> public int >> compare(ProductOrderLine o1, ProductOrderLine o2) { >> return >> o1.getName().compareTo(o2.getName()); >> } >> }); >> assertEquals("apple", >> products.get(0).getName()); >> assertEquals(appleId, >> products.get(0).getId()); >> assertEquals("banana", >> products.get(1).getName()); >> assertEquals(beforeMerge, null == >> products.get(1).getId()); >> assertEquals("orange", >> products.get(2).getName()); >> assertEquals(orangeId, >> products.get(2).getId()); >> assertEquals("pear", >> products.get(3).getName()); >> assertEquals(beforeMerge, null == >> products.get(3).getId()); >> } >> >> private ProductOrder save(ProductOrder order) { >> EntityManager em = >> factory.createEntityManager(); >> em.getTransaction().begin(); >> >> ProductOrder result; >> if (order.getId() == null) { >> em.persist(order); >> result = order; >> } else { >> result = em.merge(order); >> } >> >> em.getTransaction().commit(); >> em.detach(result); >> em.close(); >> return result; >> } >> >> private ProductOrder findById(Long id) { >> EntityManager em = >> factory.createEntityManager(); >> em.getTransaction().begin(); >> ProductOrder result = >> em.find(ProductOrder.class, id); >> em.getTransaction().commit(); >> em.detach(result); >> em.close(); >> return result; >> } >> >> } >> >> <persistence xmlns="http://java.sun.com/xml/ns/persistence" >> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >> >> xsi:schemaLocation="http://java.sun.com/xml/ns/persistencepersistence_1_0.xsd" >> version="1.0"> >> >> <persistence-unit name="testPU"> >> >> <provider>org.apache.openjpa.persistence.PersistenceProviderImpl >> </provider> >> <class>entities.ProductOrder</class> >> <class>entities.ProductOrderLine</class> >> >> <exclude-unlisted-classes>true</exclude-unlisted-classes> >> <properties> >> <property >> name="openjpa.jdbc.SynchronizeMappings" value="buildSchema" /> >> <property >> name="openjpa.ConnectionDriverName" >> >> value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> >> <property >> name="openjpa.ConnectionURL" >> >> value="jdbc:sqlserver://localhost\\SQL2008:1433;databaseName=obliprototypeunittest" >> /> >> <property >> name="openjpa.ConnectionUserName" value="obliprototype" /> >> <property >> name="openjpa.ConnectionPassword" value="hidden" /> >> <property name="openjpa.Log" >> value="DefaultLevel=INFO" /> >> </properties> >> </persistence-unit> >> </persistence> >> >> >
