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> > >
