Duplicate Entries using one-To-Many through non updated Id (PrimaryKey)

883 views Asked by At

I have a question which I couldn't understand and didn't find any real solution. I have a Object Model where one class has a bunch of collection in it and the object of these collection will also have additional entities in it. All of these collections will be connected using one-to-many JoinColumn.

Using a scheduler the "Mother" Object will get new values in their collections or existing objects of the collection will be updates and at the end the new status should be written to database.

The issue I have is, that the objects in the collection will not get an ID returned by the JPA Eclipselink and therefore duplicate entries will be existing in the database. I have no clue how to solve this issue, I hope somebody of you can help me.

In the following code you see a small example program which reproduce the failure. The scheduler calls are simulated by creating three Factories and three Transaction. Between the first and the second the collection will be changed and therefore in two and tree duplicate entries will occur. If you add another round than another element will be insert in table "adresse".

Class Main:

import java.util.HashSet;
import java.util.Set;

import javax.persistence.*;

import de.testpackage.adresse;
import de.testpackage.people;
import de.testpackage.register;

public class Main {

    public static void main(String[] args) {

        // Create Set for people

        register r = new register();

        // Initial Loading Person 1
                people p = new people();
                p.setName("Person1");
                p.setZahl(10);
                    adresse a = new adresse();
                    a.setAdresse("Adresse11");
                    p.getAdresse().add(a);
                    adresse b = new adresse();
                    b.setAdresse("Adresse12");
                    p.getAdresse().add(b);
            r.addPerson(0, p);
            people p2 = new people();
            p2.setName("Person2");
            p2.setZahl(10);
                adresse a2 = new adresse();
                a2.setAdresse("Adresse21");
                p2.getAdresse().add(a2);
                adresse b2 = new adresse();
                b2.setAdresse("Adresse22");
                p2.getAdresse().add(b2);
        r.addPerson(1, p2);


        // Simulation of a Timertask first repeat
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
        EntityManager em = factory.createEntityManager();
        System.err.println("Before:" + r.getId());
        em.getTransaction().begin();
            if (r.getId() == null){
                em.persist(r);
            } else {
                em.merge(r);
            }
            em.getTransaction().commit();
        em.close();

        System.err.println("After 1:" + r.getId());

        // Adding Adressse people1
        adresse c = new adresse();
        c.setAdresse("Adresse23");
        r.getListpeople().get(1).getAdresse().add(c);


        // Simulation of a Timertask second repeat
        EntityManagerFactory factory2 = Persistence.createEntityManagerFactory("test");
        EntityManager em2 = factory.createEntityManager();

        em2.getTransaction().begin();
            if (r.getId() == null){
                em2.persist(r);
            } else {
                em2.merge(r);
            }
            em2.getTransaction().commit();
        em2.close();

        // Simulation of a Timertask third repeat
        EntityManagerFactory factory3 = Persistence.createEntityManagerFactory("test");
        EntityManager em3 = factory.createEntityManager();

        em3.getTransaction().begin();
            if (r.getId() == null){
                em3.persist(r);
            } else {
                em3.merge(r);
            }
            em3.getTransaction().commit();
        em3.close();

    }

}

Class register

package de.testpackage;

import ......

@Entity
public class register {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "register_fk")
    Map<Integer, people> Listpeople = new HashMap<>();

    public void addPerson(int i, people p){
        this.Listpeople.put(i,p);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Map<Integer, people> getListpeople() {
        return Listpeople;
    }
}

Class people

package de.testpackage;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.*;

@Entity
public class people {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private int zahl;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "people_fk")
    private Set<adresse> adresse = new HashSet<>();

    public people() {
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getZahl() {
        return zahl;
    }
    public void setZahl(int zahl) {
        this.zahl = zahl;
    }
    public Long getId() {
        return id;
    }

    public Set<adresse> getAdresse() {
        return this.adresse;
    }

    public void setAdresse(HashSet<adresse> adresse) {
        this.adresse = adresse;
    }
}

Class adresse

package de.testpackage;

import javax.persistence.*;

@Entity

    public class adresse {

        @Id
        @GeneratedValue(strategy=GenerationType.AUTO)
        private Long id;

        String Adresse;

        public adresse() {
        }

        public String getAdresse() {
            return Adresse;
        }

        public void setAdresse(String adresse) {
            Adresse = adresse;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Long getId() {
            return id;
        }
    }

Persistence.xml

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
  version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
  <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
<class>de.testpackage.people</class>
<class>de.testpackage.adresse</class>
<class>de.testpackage.register</class>
    <properties>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpatest2" />
      <property name="javax.persistence.jdbc.user" value="root" />
      <property name="javax.persistence.jdbc.password" value="root" />

      <!-- Optimize database writes using batching -->
      <property name="eclipselink.jdbc.batch-writing" value="JDBC" />

      <!-- EclipseLink should create the database schema automatically -->
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
      <property name="eclipselink.ddl-generation.output-mode" value="database" />

    <property name="eclipselink.logging.level" value="FINE"/>
    </properties>

  </persistence-unit>
</persistence> 

I hope somebody of you can help me with this issue.

BR Ralf

1

There are 1 answers

1
Chris On

The problem is in how you are using em.merge(r). Merge takes the detached entity you passed in and will merge that state into the instance/copy that it manages. It is the managed copy that has its ID assigned when the transaction commits - your 'r' instance isn't touched or changed in anyway. You need to pass back the managed instance after the transaction commits. For the code shown, all that is needed is to change em.merge(r) to

  r = em.merge(r);

giving you a handle to the referenced entities that get their IDs assigned when the transaction commits.