Search code examples
jpajavafxtableviewpersistence

Edits to tableview cell are lost


I have a simple JavaFX TableView which is populated by a JPA database query. I have one column set as editable and that works. However, after a change is made in a cell, it disappears as soon as I sort or even navigate through the TableView. Also, how would you persist these edits back to the database?

Here is the FXML file and its controller:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="600.0" prefWidth="800.0" styleClass="mainFxmlClass" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.IXS.synergyixs.ingestor.ViewUsersController">
    <stylesheets>
        <URL value="@/styles/IssueTrackingLite.css" />
    </stylesheets>
    <children>
        <TableView fx:id="loginsTable" editable="true" prefHeight="593.0" prefWidth="794.0">
            <columns>
                <TableColumn fx:id="idCol" editable="false" text="Id">
                </TableColumn>
                <TableColumn fx:id="loginCol" text="Login">
                </TableColumn>
                <TableColumn fx:id="partyCol" editable="false" text="Party ID">
                </TableColumn>
                <TableColumn fx:id="creatorCol" editable="false" text="Creator ID">
                </TableColumn>
                <TableColumn fx:id="modifierCol" editable="false" text="Modifier ID">
                </TableColumn>
            </columns>
        </TableView>
    </children>
</AnchorPane>

the controller:

package com.IXS.synergyixs.ingestor;

import com.IXS.synergyixs.ingestor.data.Usr;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.Pane;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
 * FXML Controller class
 *
 * @author Owner
 */
public class ViewUsersController implements Initializable {

    public EntityManagerFactory emf = Persistence.createEntityManagerFactory("com.mycompany_SynergyIXS-Ingestor_jar_1.0-SNAPSHOTPU");
    public EntityManager em = emf.createEntityManager();

    public ObservableList<Usr> tableData;
    public Usr currentUsr;
    /**
     * Initializes the controller class.
     */
    @FXML
    private Pane rootPane;
    @FXML
    private TableView<Usr> loginsTable;

    @FXML
    private TableColumn<Usr, Number> idCol;

    @FXML
    private TableColumn<Usr, String> loginCol;

    @FXML
    private TableColumn<Usr, Number> partyCol;

    @FXML
    private TableColumn<Usr, Number> creatorCol;

    @FXML
    private TableColumn<Usr, Integer> modifierCol;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        /* to verify query is returning data
        for (Usr u : userList) {
            System.out.println(u.getId() + " " + u.getLogin());
        }
         */
        idCol.setCellValueFactory(new PropertyValueFactory<>("id"));

        loginCol.setCellValueFactory(new PropertyValueFactory<>("login"));
        loginCol.setCellFactory(TextFieldTableCell.forTableColumn‌());

        partyCol.setCellValueFactory(new PropertyValueFactory<>("partyId"));

        creatorCol.setCellValueFactory(new PropertyValueFactory<>("creatorId"));

        modifierCol.setCellValueFactory(new PropertyValueFactory<>("modifierId"));
        updateUserList();
        loginsTable.setItems(tableData);
    }

    public void updateUserList() {
        List<Usr> userList = em.createNamedQuery("Usr.findAll").getResultList();
        if (tableData == null) {
            tableData = FXCollections.observableArrayList(userList);
        } else {
            tableData.clear();
            tableData.addAll(userList);
        }
    }
    /*
    public void saveUser()
    {
        em.getTransaction().begin();
        em.persist(currentUsr);
        em.getTransaction().commit();
        updateUserList();
    }
     */
}​

Usr class:

package com.IXS.synergyixs.ingestor.data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

/**
 *
 * @author Owner
 */
@Entity
@Table(name = "USR", catalog = "", schema = "ADMIN")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Usr.findAll", query = "SELECT u FROM Usr u")
    , @NamedQuery(name = "Usr.findById", query = "SELECT u FROM Usr u WHERE u.id = :id")
    , @NamedQuery(name = "Usr.findAllLogin", query = "SELECT u.login FROM Usr u")
    , @NamedQuery(name = "Usr.findByLogin", query = "SELECT u FROM Usr u WHERE u.login = :login")
    , @NamedQuery(name = "Usr.findByPwd", query = "SELECT u FROM Usr u WHERE u.pwd = :pwd")
    , @NamedQuery(name = "Usr.findByPartyId", query = "SELECT u FROM Usr u WHERE u.partyId = :partyId")
    , @NamedQuery(name = "Usr.findByCreatorId", query = "SELECT u FROM Usr u WHERE u.creatorId = :creatorId")
    , @NamedQuery(name = "Usr.findByCreationDttm", query = "SELECT u FROM Usr u WHERE u.creationDttm = :creationDttm")
    , @NamedQuery(name = "Usr.findByModifierId", query = "SELECT u FROM Usr u WHERE u.modifierId = :modifierId")
    , @NamedQuery(name = "Usr.findByModificationDttm", query = "SELECT u FROM Usr u WHERE u.modificationDttm = :modificationDttm")})
public class Usr implements Serializable {

    private static final long serialVersionUID = 1L;
    // @Max(value=?)  @Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation
    @Id
    @Basic(optional = false)
    @Column(name = "ID")
    private BigDecimal id;
    @Basic(optional = false)
    @Column(name = "LOGIN")
    private String login;
    @Basic(optional = false)
    @Column(name = "PWD")
    private String pwd;
    @Column(name = "PARTY_ID")
    private BigInteger partyId;
    @Basic(optional = false)
    @Column(name = "CREATOR_ID")
    private BigInteger creatorId;
    @Basic(optional = false)
    @Column(name = "CREATION_DTTM")
    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDttm;
    @Basic(optional = false)
    @Column(name = "MODIFIER_ID")
    private BigInteger modifierId;
    @Basic(optional = false)
    @Column(name = "MODIFICATION_DTTM")
    @Temporal(TemporalType.TIMESTAMP)
    private Date modificationDttm;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "usr")
    private List<UsrAppRole> usrAppRoleList;

    public Usr() {
    }

    public Usr(BigDecimal id) {
        this.id = id;
    }

    public Usr(BigDecimal id, String login, String pwd, BigInteger creatorId, Date creationDttm, BigInteger modifierId, Date modificationDttm) {
        this.id = id;
        this.login = login;
        this.pwd = pwd;
        this.creatorId = creatorId;
        this.creationDttm = creationDttm;
        this.modifierId = modifierId;
        this.modificationDttm = modificationDttm;
    }

    public BigDecimal getId() {
        return id;
    }

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

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public BigInteger getPartyId() {
        return partyId;
    }

    public void setPartyId(BigInteger partyId) {
        this.partyId = partyId;
    }

    public BigInteger getCreatorId() {
        return creatorId;
    }

    public void setCreatorId(BigInteger creatorId) {
        this.creatorId = creatorId;
    }

    public Date getCreationDttm() {
        return creationDttm;
    }

    public void setCreationDttm(Date creationDttm) {
        this.creationDttm = creationDttm;
    }

    public BigInteger getModifierId() {
        return modifierId;
    }

    public void setModifierId(BigInteger modifierId) {
        this.modifierId = modifierId;
    }

    public Date getModificationDttm() {
        return modificationDttm;
    }

    public void setModificationDttm(Date modificationDttm) {
        this.modificationDttm = modificationDttm;
    }

    @XmlTransient
    public List<UsrAppRole> getUsrAppRoleList() {
        return usrAppRoleList;
    }

    public void setUsrAppRoleList(List<UsrAppRole> usrAppRoleList) {
        this.usrAppRoleList = usrAppRoleList;
    }

    @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 Usr)) {
            return false;
        }
        Usr other = (Usr) 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 "com.mycompany.synergyixs.ingestor.Usr[ id=" + id + " ]";
    }

}

Solution

  • The default onEditCommit handler assumes the table's model uses the JavaFX properties pattern (see TableView documentation, section titled "Editing"). Since your Usr class is essentially a plain JavaBean, with no property accessor methods, the default handler reduces to a no-op.

    You either need to refactor your Usr class so that it uses JavaFX properties (see Using javafx.beans properties in model classes for a discussion on using JavaFX properties in JPA entities), or set an onEditCommit handler on the column:

    loginCol.setOnEditCommit(e -> {
        Usr usr = e.getRowValue();
        usr.setLogin(e.getNewValue());
    });
    

    To store the value in the database, use merge(...):

    loginCol.setOnEditCommit(e -> {
        Usr usr = e.getRowValue();
        usr.setLogin(e.getNewValue());
        // merge change to database:
        Usr mergedUsr = em.merge(usr);
        // ensure table has same reference as ORM cache:
        loginsTable.set(e.getTablePosition().getRow(), mergedUsr);
    });