Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA merge() updates only some fields. Other changes are not recognized

This is my first post. So, I hope the question is exciting. I have a java program that uses Swing+JPA to work with a PostgreSQL database. I use EclipseLink JPA 2.0 as my Persistence Provider. My Entity Class was automatically generated by Netbeans 7.2.1

The problem I facing is: during a updating, I change four fields of an object retrieved using find(), then I use merge() to update the object in the database. Three of four changes are recognized and updated in the table, but one of these is not updated.

I tried several things to get around this problem: I tried to change the options related to the synchronization strategy of my persistent unit, change the line position in the code (as other fields are updated), I also tried to prefix the Entity Class field with annotation @Basic(optional = false).. Any one of my attemps worked.

Here is the code of my Entity Class (Senha.java):

package model;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author Diego Dias
 */
@Entity
@Table(name = "senha")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Senha.findAll", query = "SELECT s FROM Senha s"),
    @NamedQuery(name = "Senha.findById", query = "SELECT s FROM Senha s WHERE s.id = :id"),
    @NamedQuery(name = "Senha.findByGuiche", query = "SELECT s FROM Senha s WHERE s.guiche = :guiche"),
    @NamedQuery(name = "Senha.findByStatus", query = "SELECT s FROM Senha s WHERE s.status = :status"),
    @NamedQuery(name = "Senha.findByHchamada", query = "SELECT s FROM Senha s WHERE s.hchamada = :hchamada"),
    @NamedQuery(name = "Senha.findByAtendente", query = "SELECT s FROM Senha s WHERE s.atendente = :atendente"),
    @NamedQuery(name = "Senha.findByHcriacao", query = "SELECT s FROM Senha s WHERE s.hcriacao = :hcriacao"),
    @NamedQuery(name = "Senha.findByDtcriacao", query = "SELECT s FROM Senha s WHERE s.dtcriacao = :dtcriacao"),
    @NamedQuery(name = "Senha.findByNumeracao", query = "SELECT s FROM Senha s WHERE s.numeracao = :numeracao"),
    @NamedQuery(name = "Senha.findByNchamadas", query = "SELECT s FROM Senha s WHERE s.nchamadas = :nchamadas"),
    @NamedQuery(name = "Senha.findByPainel", query = "SELECT s FROM Senha s WHERE s.painel = :painel"),
    @NamedQuery(name = "Senha.findMaxNumeracaoByDtcriacao", query = "SELECT MAX(s.numeracao) FROM Senha s WHERE s.dtcriacao = :dtcriacao"),
    @NamedQuery(name = "Senha.findByStatusAndHchamadaAndHcriacao", query = "SELECT s FROM Senha s WHERE s.status = :status AND s.hchamada <= :hchamada AND s.hcriacao >= :hcriacao")})
public class Senha implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Integer id;
    @Column(name = "guiche")
    private String guiche;
    @Column(name = "status")
    private Character status;
    @Column(name = "hchamada")
    @Temporal(TemporalType.TIME)
    private Date hchamada;
    @Column(name = "atendente")
    private Integer atendente;
    @Column(name = "hcriacao")
    @Temporal(TemporalType.TIME)
    private Date hcriacao;
    @Column(name = "dtcriacao")
    @Temporal(TemporalType.DATE)
    private Date dtcriacao;
    @Column(name = "numeracao")
    private Integer numeracao;
    @Column(name = "nchamadas")
    private Integer nchamadas;
    @Column(name = "painel")
    private Boolean painel;

    public Senha() {
    }

    public Senha(Integer id) {
        this.id = id;
    }

    public Integer getId() {
        return id;
    }

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

    public String getGuiche() {
        return guiche;
    }

    public void setGuiche(String guiche) {
        this.guiche = guiche;
    }

    public Character getStatus() {
        return status;
    }

    public void setStatus(Character status) {
        this.status = status;
    }

    public Date getHchamada() {
        return hchamada;
    }

    public void setHchamada(Date hchamada) {
        this.hchamada = hchamada;
    }

    public Integer getAtendente() {
        return atendente;
    }

    public void setAtendente(Integer atendente) {
        this.atendente = atendente;
    }

    public Date getHcriacao() {
        return hcriacao;
    }

    public void setHcriacao(Date hcriacao) {
        this.hcriacao = hcriacao;
    }

    public Date getDtcriacao() {
        return dtcriacao;
    }

    public void setDtcriacao(Date dtcriacao) {
        this.dtcriacao = dtcriacao;
    }

    public Integer getNumeracao() {
        return numeracao;
    }

    public void setNumeracao(Integer numeracao) {
        this.numeracao = numeracao;
    }

    public Integer getNchamadas() {
        return nchamadas;
    }

    public void setNchamadas(Integer nchamadas) {
        this.nchamadas = nchamadas;
    }

    public Boolean getPainel() {
        return painel;
    }

    public void setPainel(Boolean painel) {
        this.painel = painel;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Senha)) {
            return false;
        }
        Senha other = (Senha) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "model.Senha[ id=" + id + " ]";
    }

}

Here is the relevant code of the method that updates my object. The code inside ... does not affect the object.

EntityManagerFactory emf = ctrlCreateEntityManager();
EntityManager em = emf.createEntityManager();

...

current = em.find(Senha.class, getCurrentId());
if (current.getStatus().equals('W')) {
    em.getTransaction().begin();
    current.setStatus(current.getNchamadas() >= 2 ? 'L' : current.getStatus());
    current.setHchamada(Calendar.getInstance().getTime());
    current.setNchamadas(current.getNchamadas() + 1);
    current.setPainel(true);
    em.merge(current);
    em.getTransaction().commit();
    frame.getLbNumeroSenha().setText(formatSenha(current.getNumeracao()));
} 
... 
em.close();
emf.close();

When I look in the database, the fields Status Hchamada, Nchamadas are updated, but the field Painel (of type boolean), is not updated. Follow I present a excerpt of the logging from JPA/EclipseLink:

**begin transaction**

**[EL Fine]**: 
Thread(Thread[AWT-EventQueue-1,4,file:...-threadGroup])--UPDATE senha SET
 hchamada = ?, nchamadas = ? WHERE (id = ?)
    bind => [02:15:49, 2, 4]

**[EL Finer]**:
Thread(Thread[AWT-EventQueue-1,4,file:...-threadGroup])

**commit transaction**

Looking in the code, I put a if before change the value to see if the entity managed by JPA has already the value that I want to set. Here is the code modified:

EntityManagerFactory emf = ctrlCreateEntityManager();
EntityManager em = emf.createEntityManager();

...

current = em.find(Senha.class, getCurrentId());
if (current.getStatus().equals('W')) {
    em.getTransaction().begin();
    current.setStatus(current.getNchamadas() >= 2 ? 'L' : current.getStatus());
    current.setHchamada(Calendar.getInstance().getTime());
    current.setNchamadas(current.getNchamadas() + 1);
    if (current.getPainel()) {
        System.out.println("Painel is already true");
    } 
    current.setPainel(true);
    em.merge(current);
    em.getTransaction().commit();
    frame.getLbNumeroSenha().setText(formatSenha(current.getNumeracao()));
} 
... 
em.close();
emf.close();

When I run the code, I got the message that indicates that my entity is already set with the value I want to write in database. So, I think the JPA/EclipseLink does not update the value if the it does not change in relation to the entity class managed by it. However, before run the code, I manually updated the database field painel to false. THen:

  1. I did not understand why this update is not recognized when retrieve the entity.
  2. How to force JPA to update all fields (even that do not change)?

POSSIBLE (NOT SO GOOD) SOLUTION:

Just in the moment of post this message, I realized a possible solution (not so good). Modify the code before the update to remove the entity from the database (giving a commit), and then start other transaction, updating the object. It is, removing and persisting the object again. It works.

Before I tried use remove() and merge() inside the same transaction, but it did not work.

like image 733
Diego Dias Avatar asked Nov 04 '22 02:11

Diego Dias


1 Answers

Depending of the implementation, EclipseLink in your case, JPA will always insert all mapped columns and will only update the columns that have changed. If alternate operations are required then the queries used for these operations can be customized using API, SQL, or Stored Procedures.

You can modify this behaviour with Hibernate dynamicInsert and dynamicUpdate, but you cannot with EclipseLink. These migration guides tells you that.

  • http://wiki.eclipse.org/EclipseLink/Examples/JPA/Migration/Hibernate/V3Annotations#Dynamic_Insert.2FUpdate
  • http://www.eclipse.org/eclipselink/documentation/2.5/solutions/migrhib002.htm
like image 138
Carlos Quijano Avatar answered Nov 09 '22 12:11

Carlos Quijano