Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Put together JavaFX properties and JPA Entities (NO MIXED MODE)

Tags:

jpa

javafx

Base

I have a mysql DB managed by JPA (EclipseLink) (Entities and Controllers + persistence unit). The GUI is JavaFX based.

Informations

I saw this articles:

  • One Bean to Bind Them All
  • JavaFX 2.0 Programming Model
  • JavaFX properties in JPA

and this sample code

  • Example of JPA entities that use JavaFX properties

Problem

Currently i'm using a my kind of Adapter (not real Adapter pattern) to translate a JPAEntity to JavaFX Bean

public <T, S> Function<T, S> getRefactor() {
    return o -> {
        Object rtn = null;

        //adapt **o** it to JavaFX bean

        return (S) rtn;
    };
}

and it's not the best solution, i think.

Question NO MIXED MODE ! I believe that using javafx property on the server side, is crazy, even with the super-lazy implementation.

There is a flexible solution to have all benefits of JavaFX Bean, for example bidirectional binding, and leave unchanged JPA Entities code?

EDITED

i.e. at present i have JPAEntity + JPAController and FXClass, which "represents" the JPAEntity.

JPAEntity is an old style POJO, contains data to write to DB.

FXClass has javafx properties, contains data to show in FX environment.

So... i'm using an intermediate layer to put in communication the two.

Thanks in advance

like image 912
O_T Avatar asked Aug 11 '15 12:08

O_T


2 Answers

I would generally advocate using JavaFX properties in JPA entities: I really see no obvious reason not to do so.

However, if you want to avoid doing so, you can use JavaBeanPropertyAdapters. These are adapters that create JavaFX observable properties wrapping regular JavaBean properties. So if you have a bean class

@Entity
public class Person {

    private String firstName ;
    private String lastName ;

    @Id
    private Integer id ;

    public String getFirstName() {
        return firstName ;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName ;
    }

    public String getLastName() {
        return lastName ;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName ;
    }
}

Then you can do something like

TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(cellData -> {
    try {
        return JavaBeanStringPropertyBuilder.create()
            .bean(cellData.getValue())
            .name("firstName")
            .build();
    } catch (NoSuchMethodException exc) {
        throw new RuntimeException(exc);
    }
});

This will create a JavaFX property for use in the table, and unidirectionally bind the JavaBean property to it: i.e. if you change the value in the table, the JavaBean will be updated. The reverse binding will not occur with this set up, i.e. changing the value in the bean will not update the value displayed in the table.

If you want bidirectional binding, your bean will need to support property change listeners:

public class Person {
    private String firstName ;
    private String lastName ;

    private PropertyChangeSupport pcs ;

    public Person() {
        pcs = = new PropertyChangeSupport(this);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        String oldName = this.firstName ;
        this.firstName = firstName;
        pcs.firePropertyChange("firstName", oldName, this.firstName);
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        String oldName = this.lastName ;
        this.lastName = lastName;
        pcs.firePropertyChange("lastName", oldName, this.lastName);
    }

}

Now changes to the bean will propagate to the JavaFX property used by the table, as well as vice-versa.

like image 122
James_D Avatar answered Oct 20 '22 09:10

James_D


Another possible embedded, built-in, AFAIK solution.

From Adapter pattern :

...allows the interface of an existing class to be used from another interface. It is often used to make existing classes work with others without modifying their source code.

This example is for informational purposes only and is not a confirmed solution, but need to write code well structured and flexibly to changes. So...

Example:

if we have a JPAEntity like

@Entity
@Table(name="EntityClass", uniqueConstraints = {
    @UniqueConstraint(columnNames = {"ID"})})
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "EntityClass.findAll", query = "SELECT a FROM EntityClass a"),
    @NamedQuery(name = "EntityClass.findById", query = "SELECT a FROM EntityClass a WHERE a.id = :id"),
    @NamedQuery(name = "EntityClass.findByYear", query = "SELECT a FROM EntityClass a WHERE a.year = :year")})
public class EntityClass implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(nullable = false)
    private Integer id;
    @Basic(optional = false)
    @Column(nullable = false, length = 4)
    private String year;
    //...and others

    private static final long serialVersionUID = 1L;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "fKYear")
    private Collection<Some1> some1Collection;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "fKYear")
    private Collection<Some2> some2Collection;

    public EntityClass() {
    }

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

    public EntityClass(Integer id, String year) {
        this.id = id;
        this.year = year;
    }

    public Integer getId() {
        return id;
    }

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

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    @XmlTransient
    public Collection<Some1> getSome1Collection() {
        return some1Collection;
    }

    public void setSome1Collection(Collection<Some1> some1Collection) {
        this.some1Collection = some1Collection;
    }

    @XmlTransient
    public Collection<Some2> getSome2Collection() {
        return some2Collection;
    }

    public void setSome2Collection(Collection<Some2> some2Collection) {
        this.some2Collection = some2Collection;
    }

    @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 EntityClass)) {
            return false;
        }
        EntityClass other = (EntityClass) 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 this.year;
    }
}

and we created an Interface like this

public interface IFXEntityClass{
    void setYear(String year);
    String getYear();
    //...
    void setSome1Collection(Collection<Some1> some1);
    Collection<Some1> getSome1Collection();
    //...
}

we can create our FXClass like

public class FXEntityClass implements IFXEntityClass{
    private final StringProperty yearProperty=new SimpleStringProperty();

    public StringProperty getYearProperty(){ return this.yearProperty;}
    public void setYear(String year){ this.year.set(year); }
    public String getYear(){ return this.year.get(); }
    //...
    void setSome1Collection(Collection<Some1> some1)( //do something)
    Collection<Some1> getSome1Collection(){ return null;}
    //...
}

Now we have all we needed. Let's create the Adapter.

public class EntityClassToFXEntityClassAdaptor implements IFXEntityClass{

    private EntityClass entity;
    private final StringProperty yearProperty=new SimpleStringProperty();

    public EntityClassToFXEntityClassAdaptor(EntityClass e){
        this.entity=e;
        //adapt it as you want
        this.yearProperty.set(e.getYear());
        //...
    }

    public StringProperty getYearProperty(){ return this.yearProperty;}
    public void setYear(String year){ this.year.set(year); }
    public String getYear(){ return this.year.get(); }
    //...
    void setSome1Collection(Collection<Some1> some1)( //do something)
    Collection<Some1> getSome1Collection(){ return null;}
    //...
}

And finally we can use it

EntityClass ec=something; //get an instance of JPAEntity

EntityClassToFXEntityClassAdaptor adaptor=new EntityClassToFXEntityClassAdaptor(ec);
adaptor.getYearProperty();

And a clearer example of how to apply this pattern you find in

import java.awt.*;
public class CheckboxAdapter extends Checkbox{
    public CheckboxAdapter(String n){
        super(n);
    }

    public boolean isSelected(){
        return getState();
    }

    //... continue
}
like image 42
O_T Avatar answered Oct 20 '22 09:10

O_T