6403 words

Persist error @ManyToMany how to define correct entities relationships

#1
2017-01-29 18:06

I have Product entities containing a list of Category enties. Each product can have multiple Category but they are all refering to same limited set of Category entities (via ID). There is not changes made to Category from Product. The category ID is taken from the webservice Object ID (unique too).

@Entity
 
public class Product implements Serializable {
 
    @Id @GeneratedValue
 
    private long id;
 
    @ManyToMany(cascade=CascadeType.PERSIST)
 
    private List<Category> categories = new ArrayList();
 
}
 
 
@Entity
 
public class Category implements Serializable {
 
    @Id
 
    private long id;
 
    public Category(int id) {
 
        this.id = id;      
 
    }
 
}

When adding Product as soon as a given category has been persisted already (by a previous product) it throws persist error. Now my question is : what should i do to avoid that? i want Product to contain some kind of "reference" to the category entities rather than trying to save these entity twice which is a waste. Let's say i have 20 categories and 1000 products, i want to be able to persist the 1000 products and keep Catgory table at 20 entities, not 1000 or 2000.

 

lo
lo's picture
Joined on 2015-07-12
User Post #17
#2
2017-01-30 12:46

How do you initialize the categories of a new product? You should retrieve references to existing Category instances (possibly using em.getReferencegetReference(entityClass, primaryKey)EntityManager's methodGet an instance, whose state may be lazily fetched.
See JavaDoc Reference Page...
) rather than create new Category instances.

You may also consider using merge instead of persist.

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
support
support's picture
Joined on 2010-05-03
User Post #2,684
#3
2017-01-30 15:04

Thank you for the reply. If a product is created manually from the ERP then the categories are taken from objectdb (entities are directly used in choicebox). if a prodcut is retrieved from webservice then the categories are created but category webservice ID are used as index (and they may already exist in objectdb). I will try with merge, this is probably what i need here.

lo
lo's picture
Joined on 2015-07-12
User Post #18
#4
2017-01-30 16:01

I just tried with merge, it kinda works except now no category entity is created, the table is empty, however categories are well present within Product (as if they were embeddable entities) i added screenshot of the explorer

 

package testodb;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Persistence;
 
public class TestODB_C {
 
    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }
    public static class Test{
        EntityManagerFactory emf;
        EntityManager em;
        void test(){
 
            Category cat1 = new Category(1);
            Category cat2 = new Category(2);
            Category cat3 = new Category(3);           
            Product prod1 = new Product(1,cat1);
            Product prod2 = new Product(2,cat2);
            Product prod3 = new Product(3,cat1);
 
            openDB();
            addProduct(prod1); // cat 1
            addProduct(prod2); // cat 2    
            closeDB();            
 
            openDB();
            addProduct(prod3); // cat 1 again       
            closeDB();                     
        }
        @Entity public static class Product implements Serializable {
 
            @Id //@GeneratedValue
            private long id;
            @ManyToMany(cascade=CascadeType.PERSIST)
            private List<Category> categories = new ArrayList();
            public Product(int id,Category cat) {
                this.id = id; 
                this.categories.add(cat);
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
        }
        @Entity public static class Category implements Serializable {
 
            @Id
            private long id;
            public Category(int id) {
                this.id = id;      
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
        }  
        synchronized void addProduct(Product prod){
            checkActive();          
            //em.persist(prod);  
            em.merge(prod);
            em.getTransaction().commit();            
            System.out.println("add product id : "+prod.id);
        }     
        synchronized void openDB(){           
            emf = Persistence.createEntityManagerFactory(
                    "objectdb:$objectdb/db/testc.tmp"); //;drop
            em = emf.createEntityManager();
        }       
        synchronized void closeDB(){
            em.close();
            emf.close();
        }
        void checkActive(){
            if(!em.getTransaction().isActive()) em.getTransaction().begin(); 
        }        
    }
}

 

 

lo
lo's picture
Joined on 2015-07-12
User Post #19
#5
2017-01-30 16:08

 

New try with @Genreated index and @Unique category ID, 1 category entity is created in the table on 2, and it is not filled correctly, the other category is ignored.

 

package testodb;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.jdo.annotations.Unique;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Persistence;
 
public class TestODB_C {
 
    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }
    public static class Test{
        EntityManagerFactory emf;
        EntityManager em;
        void test(){
 
            Category cat1 = new Category(1);
            Category cat2 = new Category(2);
            Category cat3 = new Category(3);           
            Product prod1 = new Product(1,cat1);
            Product prod2 = new Product(2,cat2);
            Product prod3 = new Product(3,cat1);
 
            openDB();
            addProduct(prod1); // cat 1
            addProduct(prod2); // cat 2    
            closeDB();            
 
            openDB();
            addProduct(prod3); // cat 1 again       
            closeDB();                     
        }
        @Entity public static class Product implements Serializable {
 
            @Id //@GeneratedValue
            private long id;
            @ManyToMany(cascade=CascadeType.PERSIST)
            private List<Category> categories = new ArrayList();
            public Product(int id,Category cat) {
                this.id = id; 
                this.categories.add(cat);
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
        }
        @Entity public static class Category implements Serializable {
 
            @Id @GeneratedValue
            private long id;
            @Unique
            private int catId;
            public Category(int id) {
                this.catId = id;      
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
            public int getCatId() {
                return catId;
            }
            public void setCatId(int catId) {
                this.catId = catId;
            }
        }  
        synchronized void addProduct(Product prod){
            checkActive();   
            //em.persist(prod);
            em.merge(prod);           
            em.getTransaction().commit();            
            System.out.println("add product id : "+prod.id);
        }     
        synchronized void openDB(){           
            emf = Persistence.createEntityManagerFactory(
                    "objectdb:$objectdb/db/testc.tmp"); //;drop
            em = emf.createEntityManager();
        }       
        synchronized void closeDB(){
            em.close();
            emf.close();
        }
        void checkActive(){
            if(!em.getTransaction().isActive()) em.getTransaction().begin(); 
        }        
    }
}
lo
lo's picture
Joined on 2015-07-12
User Post #20
#6
2017-01-30 16:28

I finally managed to get it working by using merge when commit fails on persist and using @Generated indexes for both entities, there seem to be no way of achieving what i need without keeping generated indexes.

 

package testodb;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.jdo.annotations.Unique;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
 
public class TestODB_C {
 
    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }
    public static class Test{
        EntityManagerFactory emf;
        EntityManager em;
        void test(){
 
            Category cat1 = new Category(1);
            Category cat2 = new Category(2);
            Category cat3 = new Category(3);           
            Product prod1 = new Product(1,cat1);
            Product prod2 = new Product(2,cat2);
            Product prod3 = new Product(3,cat1);
 
            openDB();
            addProduct(prod1); // cat 1
            addProduct(prod2); // cat 2    
            closeDB();            
 
            openDB();
            addProduct(prod3); // cat 1 again       
            closeDB();                     
        }
        @Entity public static class Product implements Serializable {
 
            @Id @GeneratedValue
            private long id;
            private int prodId;
            @ManyToMany(cascade=CascadeType.PERSIST)
            private List<Category> categories = new ArrayList();
            public Product(int id,Category cat) {
                this.prodId = id; 
                this.categories.add(cat);
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
            public int getProdId() {
                return prodId;
            }
            public void setProdId(int prodId) {
                this.prodId = prodId;
            }
        }
        @Entity public static class Category implements Serializable {
 
            @Id @GeneratedValue
            private long id;
            @Unique
            private int catId;
            public Category(int id) {
                this.catId = id;      
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
            public int getCatId() {
                return catId;
            }
            public void setCatId(int catId) {
                this.catId = catId;
            }
        }  
        synchronized void addProduct(Product prod){
            System.out.println("add product id : "+prod.id);
            checkActive();   
            em.persist(prod);                       
            try{em.getTransaction().commit();}
            catch( PersistenceException ex){
                System.out.println("Transaction must be merged : ["+ prod.getId()+"]");
                checkActive();
                em.merge(prod);
                em.getTransaction().commit();               
            }                      
        }     
        synchronized void openDB(){           
            emf = Persistence.createEntityManagerFactory(
                    "objectdb:$objectdb/db/testc.tmp"); //;drop
            em = emf.createEntityManager();
        }       
        synchronized void closeDB(){
            em.close();
            emf.close();
        }
        void checkActive(){
            if(!em.getTransaction().isActive()) em.getTransaction().begin(); 
        }        
    }
}
lo
lo's picture
Joined on 2015-07-12
User Post #21
#7
2017-01-30 17:24

If you use merge, you many want to change the cascade setting, which is currently:

    @ManyToMany(cascade=CascadeType.PERSIST)

to include CascadeType.MERGE:

    @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.PMERGE})

 

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
support
support's picture
Joined on 2010-05-03
User Post #2,686
#8
2017-01-30 18:26

Thank you, i overlooked that. I finally got it fully working with no @Generated annotation, I had to remove the method and  @ManyToMany on Product in Order otherwise it went into objectdb internal error. Now everything is persisted automatically everywhere, it merges when expected, it uses provided indexes and there is no more doublons, great!

 

package testodb;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
 
public class TestODB_sameID implements Serializable {
 
    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }
    public static class Test{
        EntityManagerFactory emf;
        EntityManager em;
        void test(){
 
            Category cat1 = new Category(1);
            Category cat2 = new Category(2);
            Category cat3 = new Category(3);
 
            Customer cust1 = new Customer(1);
            Customer cust2 = new Customer(2);
            Customer cust3 = new Customer(3);
 
            Product prod1 = new Product(12,cat1);
            Product prod2 = new Product(22,cat2);
            Product prod3 = new Product(32,cat1);
 
            Order ord1 = new Order(1,cust1,prod1);
            Order ord2 = new Order(2,cust2,prod2);
            Order ord3 = new Order(3,cust3,prod3);
            Order ord4 = new Order(4,cust1,prod3);
            Order ord5 = new Order(5,cust2,prod2);
            Order ord6 = new Order(6,cust3,prod2);
 
            openDB();
                addProduct(prod1);
                addProduct(prod2);
                addProduct(prod3);
            closeDB();
 
            openDB(); 
                addOrder(ord1);
                addOrder(ord2);
                addOrder(ord3);
            closeDB();
 
            openDB(); 
                addOrder(ord4);
                addOrder(ord5);
                addOrder(ord6);
            closeDB();                  
        }
        @Entity public static class Product implements Serializable {
 
            @Id
            private long id;
            @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
            private List<Category> categories = new ArrayList();
            public Product(int id,Category cat) {
                this.id = id; 
                this.categories.add(cat);
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
        }
        @Entity public static class Order implements Serializable {
 
            @Id
            private long id;
            @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
            private Customer cust;
            //@ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
            private List<Product> orderRow = new ArrayList();
            public Order(long id,Customer cust,Product prod) {
                this.id = id;
                this.cust = cust;
                this.orderRow.add(prod);
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
        } 
        @Entity public static class Category implements Serializable {
 
            @Id
            private long id;
            public Category(int id) { 
                this.id = id;
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
        }        
        @Entity public static class Customer implements Serializable {
 
            @Id
            private long id;
            public Customer(int id) { 
                this.id = id;
            }
            public long getId() {
                return id;
            }
            public void setId(long id) {
                this.id = id;
            }       
        }       
        synchronized void addProduct(Product prod){
            System.out.println("add product ["+ prod.getId()+"]");
            checkActive();   
            em.persist(prod);                       
            try{em.getTransaction().commit();}
            catch( PersistenceException ex){
                System.out.println("product ["+ prod.getId()+"] must be merged");
                checkActive();
                em.merge(prod);
                em.getTransaction().commit();               
            }                      
        } 
        synchronized void addOrder(Order ord){
            System.out.println("add order ["+ ord.getId()+"]");
            checkActive();   
            em.persist(ord);                       
            try{em.getTransaction().commit();}
            catch( PersistenceException ex){
                System.out.println("order ["+ ord.getId()+"] must be merged");
                checkActive();
                em.merge(ord);
                em.getTransaction().commit();               
            }                      
        }        
        synchronized void openDB(){           
            emf = Persistence.createEntityManagerFactory(
                    "objectdb:$objectdb/db/testc.tmp"); //;drop
            em = emf.createEntityManager();
        }       
        synchronized void closeDB(){
            em.close();
            emf.close();
        }
        void checkActive(){
            if(!em.getTransaction().isActive()) em.getTransaction().begin(); 
        }        
    }
}
lo
lo's picture
Joined on 2015-07-12
User Post #22

Post Reply

Please read carefully the posting instructions - before posting to the ObjectDB website.

  • You may have to disable pop up blocking in order to use the toolbar (e.g. in Chrome).
  • Use ctrl + right click to open the browser context menu in the editing area (e.g. for using a browser spell checker).
  • To insert formatted lines (e.g. Java code, stack trace) - select a style in the toolbar and then insert the text in the new created block.
  • Avoid overflow of published source code examples by breaking long lines.
  • You may mark in paragraph code words (e.g. class names) with the code style (can be applied by ctrl + D).
  • Long stack traces (> 50 lines) and complex source examples (> 100 lines) should be posted as attachments.
Attachments:
Maximum file size: 32 MB
Cancel