5

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:

and this sample code

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

O_T
  • 379
  • 3
  • 14
  • 1
    What is the objection to using JavaFX properties (which are merely an extension of the standard JavaBean properties) in a JPA entity? (Another example of someone using this is [Adam Bien's airhacks](https://github.com/AdamBien/airhacks-control/blob/master/src/main/java/com/airhacks/control/business/registrations/entity/Attendee.java).) Any other solution necessarily introduces another layer in your model. If you really want to avoid it, the best way is probably to use `JavaBeanPropertyAdapter`s. See http://stackoverflow.com/questions/23522130/javafx-properties-wrapping-bean. – James_D Aug 11 '15 at 12:55
  • I'm using that `UIFX.getTWColumn("ColumnName", 400.0, (TableColumn.CellDataFeatures p) -> new ReadOnlyObjectWrapper(p.getValue()), (TableColumn p) -> new ClassFXContent().getClassTableCellFactory() )` – O_T Aug 11 '15 at 12:59
  • I don't understand that... What is `UIFX`? How does that relate to `JavaBeanPropertyAdapter`s? – James_D Aug 11 '15 at 13:03
  • 'UIFX.getTWColumn(...)' is a static method to create TableCell content. Sorry, i'm not using **JavaBeanPropertyAdapter** – O_T Aug 11 '15 at 13:03
  • +1 **JavaBeanPropertyAdapter** is an acceptable solution. Any others ? – O_T Aug 11 '15 at 13:11
  • That's the only built-in solution I know. You could probably write something a little less legacy-looking than providing the property name as a `String` (e.g. providing the getter and setter as lambdas) but AFAIK there's no built-in solution for that. – James_D Aug 11 '15 at 13:42
  • What is your communication layer between the server and the UI? Is it possible to implement the same interface between the UI and server but have different implementations so that one uses JavaFX properties and one uses Strings. I have implemented something similar using REST as the communication and simply provided specialized implementations on each side. – purring pigeon Aug 11 '15 at 13:43
  • I created a factory that provides to get data collection (from JPA), fx representation (to show JPA data in FX )and others... and it works!! :) I was looking for a way to eliminate the FX classes and work directly with JPA. – O_T Aug 11 '15 at 13:55

2 Answers2

7

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.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • When I write a JavaFX property to the database with something like EntityManager.persist it will write only the wrapped value? What happens to the bindings? – Mark Jun 02 '17 at 01:18
  • @Mark It will only write the wrapped value. The way it works is that when the entities are written, the ORM calls `getXXX()` on the bean; when they are read from the database the ORM creates a new bean by calling the default constructor and then calls `setXXX(...)` to populate it. The bindings are not stored in the database. (Which table, and which columns, would you expect them to be stored in???) It's not clear why you would want to persist and restore bindings anyway. If you have a genuine use case for that you could post a new question. – James_D Jun 02 '17 at 01:24
  • @James_D your post http://www.marshall.edu/genomicjava/2014/05/09/one-bean-to-bind-them-all/ is no longer available .. moved anywhere? I remember that it contains some nice content which I would like to re-read for a current project :) – kleopatra Jan 10 '18 at 13:16
  • @kleopatra Unfortunately IT stopped supporting personal blogs; I need to find a new home for that - just no time right now. [Steven van Impe](http://svanimpe.be/blog/properties-jpa) has similar material. – James_D Jan 10 '18 at 13:27
2

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
}
O_T
  • 379
  • 3
  • 14