简体   繁体   中英

JPA - EclipseLink make additional SELECT requests for relationships when merging an entity

I've got a question on a "strange" behaviour when updating an entity with the merge method.

I have an entity "Personne" that have two relationship (fetch = LAZY) . I have a HTML form I use to modify only fields of this entity. When saving this entity with the merge method, I see SELECT requests for all relationships.

I don't understand why these SELECTs are done and I would like to avoid them because the data loaded by this behaviour can be "huge".

There it is my entity :

@Entity
@Table(name="personne")
@NamedQuery(name="Personne.findAll", query="SELECT p FROM Personne p")
public class Personne implements Serializable {

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(unique=true, nullable=false)
private Integer id;

@Temporal(TemporalType.TIMESTAMP)
@Column(name="date_naissance")
private Date dateNaissance;

@Column(length=75)
private String denomination;

@Column(length=75)
private String nom;

@Column(precision=10, scale=2)
private BigDecimal poids;

@Column(length=75)
private String prenom;

@Column(precision=10, scale=2)
private BigDecimal taille;

//bi-directional many-to-one association to Contact
@OneToMany(mappedBy="personne", fetch = FetchType.LAZY)
private List<Contact> contacts;

//bi-directional many-to-one association to Log
@OneToMany(mappedBy="personne", fetch = FetchType.LAZY)
private List<Logs> logs;

//bi-directional many-to-one association to PersonneHasAdresse
@OneToMany(mappedBy="personne", fetch = FetchType.LAZY)
private List<PersonneHasAdresse> personneHasAdresses;

//bi-directional many-to-one association to PersonneHasPersonne
@OneToMany(mappedBy="parent", fetch = FetchType.LAZY)
private List<PersonneHasPersonne> personneHasPersonnesParent;

//bi-directional many-to-one association to PersonneHasPersonne
@OneToMany(mappedBy="child", fetch = FetchType.LAZY)
private List<PersonneHasPersonne> personneHasPersonnesChild;

public Personne() {
}

public Integer getId() {
    return this.id;
}

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

public Date getDateNaissance() {
    return this.dateNaissance;
}

public void setDateNaissance(Date dateNaissance) {
    this.dateNaissance = dateNaissance;
}

public String getDenomination() {
    return this.denomination;
}

public void setDenomination(String denomination) {
    this.denomination = denomination;
}

public String getNom() {
    return this.nom;
}

public void setNom(String nom) {
    this.nom = nom;
}

public BigDecimal getPoids() {
    return this.poids;
}

public void setPoids(BigDecimal poids) {
    this.poids = poids;
}

public String getPrenom() {
    return this.prenom;
}

public void setPrenom(String prenom) {
    this.prenom = prenom;
}

public BigDecimal getTaille() {
    return this.taille;
}

public void setTaille(BigDecimal taille) {
    this.taille = taille;
}

@JsonIgnore
public List<Contact> getContacts() {
    return this.contacts;
}

public void setContacts(List<Contact> contacts) {
    this.contacts = contacts;
}

public Contact addContact(Contact contact) {
    this.getContacts().add(contact);
    contact.setPersonne(this);

    return contact;
}

public Contact removeContact(Contact contact) {
    this.getContacts().remove(contact);
    contact.setPersonne(null);

    return contact;
}

@JsonIgnore
public List<Logs> getLogs() {
    return this.logs;
}

public void setLogs(List<Logs> logs) {
    this.logs = logs;
}

public Logs addLog(Logs log) {
    this.getLogs().add(log);
    log.setPersonne(this);

    return log;
}

public Logs removeLog(Logs log) {
    this.getLogs().remove(log);
    log.setPersonne(null);

    return log;
}

@JsonIgnore
public List<PersonneHasAdresse> getPersonneHasAdresses() {
    return this.personneHasAdresses;
}

public void setPersonneHasAdresses(List<PersonneHasAdresse> personneHasAdresses) {
    this.personneHasAdresses = personneHasAdresses;
}

@JsonIgnore
public PersonneHasAdresse addPersonneHasAdress(PersonneHasAdresse personneHasAdress) {
    this.getPersonneHasAdresses().add(personneHasAdress);
    personneHasAdress.setPersonne(this);

    return personneHasAdress;
}

public PersonneHasAdresse removePersonneHasAdress(PersonneHasAdresse personneHasAdress) {
    this.getPersonneHasAdresses().remove(personneHasAdress);
    personneHasAdress.setPersonne(null);

    return personneHasAdress;
}

@JsonIgnore
public List<PersonneHasPersonne> getPersonneHasPersonnesParent() {
    return this.personneHasPersonnesParent;
}

public void setPersonneHasPersonnesParent(List<PersonneHasPersonne> personneHasPersonnesParent) {
    this.personneHasPersonnesParent = personneHasPersonnesParent;
}

public PersonneHasPersonne addPersonneHasPersonnesParent(PersonneHasPersonne personneHasPersonnesParent) {
    this.getPersonneHasPersonnesParent().add(personneHasPersonnesParent);
    personneHasPersonnesParent.setParent(this);

    return personneHasPersonnesParent;
}

public PersonneHasPersonne removePersonneHasPersonnesParent(PersonneHasPersonne personneHasPersonnesParent) {
    this.getPersonneHasPersonnesParent().remove(personneHasPersonnesParent);
    personneHasPersonnesParent.setParent(null);

    return personneHasPersonnesParent;
}

@JsonIgnore
public List<PersonneHasPersonne> getPersonneHasPersonnesChild() {
    return this.personneHasPersonnesChild;
}

public void setPersonneHasPersonnesChild(List<PersonneHasPersonne> personneHasPersonnesChild) {
    this.personneHasPersonnesChild = personneHasPersonnesChild;
}

public PersonneHasPersonne addPersonneHasPersonnesChild(PersonneHasPersonne personneHasPersonnesChild) {
    this.getPersonneHasPersonnesChild().add(personneHasPersonnesChild);
    personneHasPersonnesChild.setChild(this);

    return personneHasPersonnesChild;
}

public PersonneHasPersonne removePersonneHasPersonnesChild(PersonneHasPersonne personneHasPersonnesChild) {
    this.getPersonneHasPersonnesChild().remove(personneHasPersonnesChild);
    personneHasPersonnesChild.setChild(null);

    return personneHasPersonnesChild;
}

And my generic method to update object :

public void merge(E obj) {
        EntityManager em = PersitenceManager.getInstance().getEm(this.persistUnit);
        try {
            EntityTransaction t = em.getTransaction();

            try {
                t.begin();
                em.merge(obj);
                t.commit();
            } finally {
                if (t.isActive()) {
                    t.rollback();
                }
            }
        } finally {
            em.close();
        }
    }

Before the UPDATE statement, there is SELECTs for contacts and logs. Is anybody know why ? Is it a normal behaviour ? If yes, how to avoid it ?

Thanks you very much for any help !


Edit 1:

The complete code from all layers (from REST to DAO):

@Path("personnes")
@Produces({ MediaType.APPLICATION_JSON })
@Consumes({ MediaType.APPLICATION_JSON })
public class PersonnesRest {

    private PersonneServices personneServices = new PersonneServices();

    ...

    @PUT
    @Path("{id}")
    public Response update(@PathParam("id") Integer id, Personne personne) {
        if (personne == null || !id.equals(personne.getId())) {
            return Response.status(Status.BAD_REQUEST).build();
        }

        this.personneServices.merge(personne);
        return Response.noContent().build();
    }

    @POST
    public Response create(Personne personne) {
        Personne createdPersonne = this.personneServices.persist(personne);
        return Response.created(URI.create("personnes/" + createdPersonne.getId())).build();
    }

    ...

}

public class PersonneServices extends AbstractBasicServices<Personne> {

    public PersonneServices() {
        super(new PersonneDao(), Personne.class, "poi.model");
    }
}

public class PersonneDao extends AbstractBasicDao<Personne> {

}

public abstract class AbstractBasicServices<E> {

    private AbstractBasicDao<E> dao;

    private Class<E> clazz;

    private String persistUnit;

    public AbstractBasicServices(AbstractBasicDao<E> dao, Class<E> clazz, String persistUnit) {
        this.dao = dao;
        this.clazz = clazz;
        this.persistUnit = persistUnit;
    } 

public E persist(E obj) {
        EntityManager em = PersitenceManager.getInstance().getEm(this.persistUnit);
        E res = null;

        try {
            EntityTransaction t = em.getTransaction();
            try {
                t.begin();
                res = this.dao.persist(obj, em);
                t.commit();
            } finally {
                if (t.isActive()) {
                    t.rollback();
                }
            }
        } finally {
            em.close();
        }

        return res;
    }

// The merge method is the one in first post.

}

public abstract class AbstractBasicDao<T> {
    public T persist(T obj, EntityManager em) {
        em.persist(obj);
        em.flush();
        em.refresh(obj);
        return obj;
    }

    public T merge(T obj, EntityManager em) {
        return em.merge(obj);
    }
}

Edit 2:

After review, I had (in persistence.xml) the "shared cache mode" to "NONE". It was obvious the entity manager need to reload relationships (silly me...). So I set it to "Default".

I made the following tests (I reboot the server for each test) :

  • (Test 1-1) Create a new entity (persist): INSERT + SELECTs for relationships.
  • (Test 1-2) Update the previous entity (merge): UPDATE (no SELECT).
  • (Test 2-1) Update an entity (merge): UPDATE + SELECTs for relationships.
  • (Test 2-2) Update the same entity (merge): UPDATE (no SELECT).

Does it seem normal ?


Edit 3:

I posted the entire Personne entity and I see now my mistake. I put the @JsonIgnore to avoid loading of relationships when querying to display a list of Personne attributes because of the marshalling of relationships. But then the unmarshalling did not set back the indirected lists. I removed the annotations.

I created a wrapper that only contains attributes I need for JAX-RS layer and update fields to the corresponding entity when need an update and there is no more additional SELECT.

Thanks all for your time.

I cannot reproduce this.

Can you verify right before calling merge() whether the following expression returns false :

((org.eclipse.persistence.indirection.IndirectContainer)obj.getContacts()).isInstantiated()

(assuming that you have a getter named getContacts() which just returns the field contacts within your entity class).

If it returns false then this would be a "strange" behavior which needs to be further investigated.

If it returns true (which is most probably the case) then this lazy loaded collection has been accessed before hence EclipseLink needs to check whether something has changed in the database.
In this case, check which code accesses the collection beforehand (use a breakpoint to figure this out).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM