Friday, May 31, 2013

JavaFX properties in JPA entity classes

This post has been moved http://svanimpe.be/blog/properties-jpa.html

16 comments:

  1. It's a very smart idea. I wished I had discovered it earlier.
    Thanks.

    ReplyDelete
    Replies
    1. Thanks. My first idea was building pass-through properties on top of the basic attributes that were in place, but this solution is way more elegant and light-weight. But it stills needs work. If I find some time to think, I'll try to cover other cases as well.

      Delete
  2. Hey,

    the implementation first of all looks simple and functional.
    One possible problem came up my mind: What do you do if you don't want to use JPA, for example if you replace parts of your data access layer? Say - you're storing your entities in a xml file. You're going to keep using an unnecessary jar file and might make extensions of this entity very hard or even impossible.
    Wouldn't it be more clever to keep using "JavaFX POJOs" without annotations and split this whole persistence layer into interfaces?

    ReplyDelete
    Replies
    1. Hi Dennis. If you want to make your pojos JPA-mappable, but want to keep them free of annotations (in case you no longer want to use JPA), you could try configuring JPA through XML?

      Delete
    2. Right, that's a solution. I wanted to hightlight this potential problem with the data layer. I can't recommend "mixing" of pojos with any annotations. Most often it "breaks" future changes.

      Delete
  3. Can't wait to try it. I'll be sure to give some feedback.

    ReplyDelete
  4. Just wondering if the fact that JavaFX properties are not Serializable causes problems in real applications here. I can see this working fine if JPA is accessing a local database, but if you're using a data access layer exposed as a remote EJB, for example, I think this would run into problems.

    ReplyDelete
    Replies
    1. Does anyone still use remote EJBs in new projects ? Or serialisation for that matter ? With any luck, the next version of my Reminders example will even be completely EJB free ;)

      Delete
  5. Hey,

    I am pretty sure that I did everything correct and I am getting : Caused by: java.lang.IllegalAccessError: class org.apache.openjpa.util.com$sun$javafx$collections$ObservableListWrapper$ObservableSubList$0$proxy cannot access its superclass com.sun.javafx.collections.ObservableListWrapper$ObservableSubList
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:792)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
    at serp.bytecode.BCClassLoader.findClass(BCClassLoader.java:50)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:270)
    at org.apache.openjpa.util.GeneratedClasses.loadBCClass(GeneratedClasses.java:67)
    at org.apache.openjpa.util.ProxyManagerImpl.getFactoryProxyCollection(ProxyManagerImpl.java:384)

    even though I have annotated my getters and setters accordingly.

    I am pretty sure it's related to this item:

    @OneToMany(mappedBy = "project", cascade = CascadeType.PERSIST)
    public List getMillTasks() {
    return millTasksProperty.subList(0, millTasksProperty.size());
    }

    Because when I set that as transient then my code would't crash anymore.
    The very odd thing is that my DB (h2) is created correctly and all relations are filled out correctly and after the crash I even see the correct data within the DB.

    ReplyDelete
    Replies
    1. I'm not sure I'll be of much help, as I've never even seen that error before :)

      But I do wonder about the following:

      * When you declare an inverse side @OneToMany(mappedBy = "project") on e.g. a List, JPA will look for a project attribute in the MillTask class and use that attribute to handle the mapping. I'm not sure how JPA will react when you use a raw List type and don't specify the type parameter in any other way (annotation attributes).
      * Seeing as your millTasks is the inverse side of the relationship and JPA tends to ignore inverse sides, I'm not sure how JPA will react when you add a PERSIST cascade on that inverse side.

      A quick google search on your error also yields the following possible causes. Maybe they are of some help?

      * There are conflicting classes in your jars.
      * The class and it's superclass are being loaded by different class loaders.

      Delete
    2. hey Steven,

      thanks for your answer. In the mean time I got a tad further and I an beable now to at least load up the program and have all entities persisted.
      However, when stop/start the program and try to re-load then 'bam' I get very odd null-pointer exceptions.

      I have checked the conflicting jars, I came across that blogpost myself :)
      I think I will create a small self contained javafx program using OpenJPA and see if that still borks out.
      Else I am afraid I need to find a other method to load/store program info.



      Delete
    3. Steven,

      instead of using OpenJPA I am now using hibernate.
      Well, what can I say... Hibernate doesn't show any of these issues and the program happily show all data! And I though I was going crazy :s


      Delete
  6. Steven,
    I'm trying to implement this example in my own project and I wrote the following code:

    @Entity
    @Access(AccessType.FIELD)
    public abstract class Gebruiker{
    @Id
    @Column(name = "Username")
    @Basic(optional = false)
    private String username;

    //more field attributes here..

    //JavaFX Properties
    @Transient //Niet in database
    private final StringProperty voornaam = new SimpleStringProperty();

    @Access(AccessType.PROPERTY)
    @Basic(optional = false)
    @Column(name = "Voornaam")
    public String getVoornaam() {
    return voornaam.get();
    }
    public void setVoornaam(String value) {
    voornaam.set(value);
    }
    public StringProperty voornaamProperty() {
    return voornaam;
    }
    }//End of class

    The code does work, but Netbeans keeps telling me that I have no entity ID in this class..
    Do you have any solution for this?
    Thanks in advance

    ReplyDelete
    Replies
    1. If it works, I wouldn't worry about what NetBeans says, it doesn't understand all JPA mappings. But feel free to email me your project, so I can try to figure out what's going on.

      Delete
  7. One potential big problem is if you are binding the jfxbean to something that can update it through the binding, then JPA will not work, as it will not pick up that something changed, because the property is updated directly in stead of using the set method.

    You can say that you must always use the set method, but that makes jfx properties kind of useless in beans as you can not directly bind to them for something like gui binding where a user can update the name property directly using a text field on screen.

    Unless I do not understand JPA good enough as I am still learning hibernate and I wished I could use jfx properties directly.

    Maybe there is a need to update jpa for jfx property use as they are extremely powerful and useful.

    Please tell me I am wrong and that updating the property directly would update the underlying database some how or maybe there is an easy work around.

    ReplyDelete
  8. One simple work around that works for me is to create a method in your entity class that copies the contents of the other object. For example,

    public Customer copyObject(final Customer c) {
    this.setPersonId(c.getPersonId());
    this.setFirstName(c.getFirstName());
    this.setLastName(c.getLastName());
    return this;
    }

    Your FXML controller class would contain the following for binding and updating the contents.

    private Customer selectedCustomer = new Customer();

    personId.textProperty().bind(selectedCustomer.personIdProperty().asString());
    Bindings.bindBidirectional(firstName.textProperty(), selectedCustomer.firstNameProperty());
    Bindings.bindBidirectional(lastName.textProperty(), selectedCustomer.lastNameProperty());

    listView.getSelectionModel().selectedItemProperty().addListener((ObservableValue observable, CustomerListView oldValue, CustomerListView newValue) -> {
    if (newValue != null) {
    selectedCustomer.copyObject(newValue);
    }
    });

    ReplyDelete