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/persistence 
persistence_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>

Reply via email to