Menu

Readonly fields in Create and Edit

schifazl
2013-11-08
2014-10-15
1 2 > >> (Page 1 of 2)
  • schifazl

    schifazl - 2013-11-08

    I've found another bug... do you hate me? :D

    Read only fields are included in the Create and Edit forms.

    e.g.:

    public boolean isInteresting(){
        return this.young && this.pretty;
    }
    

    or:

    ~~~~~~
    public String getFullName(){
    return this.name + " " + this.surname;
    }

    are calculated, thus read only fields, so "setInteresting" and "setFullName" don't exist.

    In Create or Edit, fields Interesting and FullName are present, so the Create action fails. Commenting them out in the Create and Edit forms resolved the issue.

     
  • Kay Wrobel

    Kay Wrobel - 2013-11-08

    I don't hate you, I am actually excited somebody runs into these issues. As I said, I mostly test with reverse-engineered entity classes, and so most, if not all of those entities have read-write access fields.

    I will say, though, that you can also use the @Transient annotation. Those fields get skipped by the wizard. But let me see if there's anything I can find to detect a read-only situation.

     
  • Kay Wrobel

    Kay Wrobel - 2013-11-08

    So let me throw this question back at you? What should happen with these fields? Should they be displayed in the forms, just not as input fields? Or are they more for internal purposes and should be skipped altogether?

    I can't find any code for detecting read-only fields inside NetBeans' JpaControllerUtil class, a helper class that's being used by the wizard when making many determinations about an entity.

     

    Last edit: Kay Wrobel 2013-11-08
  • Kay Wrobel

    Kay Wrobel - 2013-11-08

    Next question: if the field is indeed read-only, is it an actual database field or is it a field calculated at runtime? If it is an actual database field, how would it be populated if you make it a read-only field?

    Let's take your example for getFullName: this is actually something that I believe would not exist as a field inside your database table, but as an internal field. That method should definitely by annotated @Transient in my opinion. But say, you have a field that comes out of a database, maybe it is being calculated by a trigger procedure inside the database. Could or should it still be allowed to be modified? Then the field would not actually be read-only but an ordinary read-write field.

    So let's say you make it @Transient. The wizard currently skips such fields. If you still wanted to generate a display field for @Transient fields, I would have to add some code to not skip them but pass them along somehow.

    What's your thought? I need some direction on this topic from you and maybe other users.

     
  • Kay Wrobel

    Kay Wrobel - 2013-11-08

    So this is interesting. I can't actually put the @Transient annotation on the method without the IDE complaining about field access and that I should unify it. So forget that for the moment. Yes, I actually created a read-only "field" like your getFullName. My persistence unit was set to create the database table for me in MySQL. And so what I found is that JPA actually didn't try to create any table field for FullName. That is good news for me since I didn't know how this situation is being handled.

    Alright, so now I have to find a way of detecting read-only fields. They're really properties then since no actual fullName field exists in the entity.

     
  • schifazl

    schifazl - 2013-11-09

    Hi! Sorry for disappearing! Do you still need my answers?

    So let me throw this question back at you? What should happen with these fields? Should they be displayed in the forms, just not as input fields? Or are they more for internal purposes and should be skipped altogether?

    That's a good question... I would say "It depends". Some getters like getFullName could be useful if they were displayed, some other fields would be better to hide them. E.g. let's say that I have

    public boolean isAvailable(){
        //make a remote query to a remote device which is
        //operated by a human and the response could
        //take several seconds to return
        return response;
    }
    

    this is definitely a field that I wouldn't show because it would take a long time to get the answer. Maybe I would too hide properties that could be confusing to the user.

    Next question: if the field is indeed read-only, is it an actual database field or is it a field calculated at runtime? If it is an actual database field, how would it be populated if you make it a read-only field?

    Usually it's a calculated field, but it could be a database field too. E.g.:

    public class Customer {
    
        @NotNull
        private String name;
        @NotNull
        private String surname;
        @NotNull
        private string fullName;
    
        public void setName(String name) {
            this.name = name
        }
    
        public String getName() {
            return name;
        }
    
        public void setSurname(String surname) {
            this.surname = surname
        }
    
        public String getSurname() {
            return surname;
        }
    
        /*
         * NOTE: only getter for fullName!
         */
        public String getFullName() {
            return fullName
        }
    
        /*
         * fullName is a calculated field, but I'm persisting it
         * because in my Application I need to do a lot of
         * queries/joins/whatever using this field.
         * Calculating it at runtime could lead to 
         * performance issues
         */
        @PrePersist
        @PreUpdate
        private prePersist() {
            fullName = this.name + " " + this.surname;
        }
    

    I hope that this example clarifies the point. Both persisted readonly field and non persisted readonly fields are possible. Please note that I wrote this code example directly in the reply windows, so it could have some errors since it isn't tested.

     
  • Kay Wrobel

    Kay Wrobel - 2013-11-09

    Thanks. So let's just keep it simple: if there's only a getter method without a setter method, it's read-only and should be displayed only. I have started work with this assumption in mind. I am currently running into issues with the templates. Here's why: if it's a read-only field, I want to apply the same template as in the View.xhtml file. But I don't want to duplicate the same template code in two places. So I was thinking of centralizing the template code for one field into a separate template and have that be included in both View.xhtml and Edit.xhtml, skip it in Create.xhtml since the record doesn't exist yet. Unfortunately, the <#include> statement doesn't find my template file. So that's where I'm at right now. Will continue working on this on Monday.

     
  • Neil Franken

    Neil Franken - 2013-11-11

    Kay while you are working on this I have something to add. JPA allows you to add the @Version annotation to the Entity class. This allows you to use optimistic locking on your database. In certain cases using this makes sense.

    When using this annotation on the example below the version number is still editable. Editing the version number will cause a exception to be thrown. Also as the version number automatically increases you will need to a way to refresh the record. I provided some sample code for you to work with.

    ~~~~~~~~~~~~~~~~~~~~~~~~
    /
    * To change this template, choose Tools | Templates
    * and open the template in the editor.
    /
    package com.test.jpa;

    import java.io.Serializable;
    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.Version;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;
    import javax.xml.bind.annotation.XmlRootElement;

    /*
    *
    * @author adm_neil
    /
    @Entity
    @Table(name = "customer")
    @XmlRootElement
    @NamedQueries({
    @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c"),
    @NamedQuery(name = "Customer.findByCustomerID", query = "SELECT c FROM Customer c WHERE c.customerID = :customerID"),
    @NamedQuery(name = "Customer.findByCustomerName", query = "SELECT c FROM Customer c WHERE c.customerName = :customerName"),
    @NamedQuery(name = "Customer.findByVersion", query = "SELECT c FROM Customer c WHERE c.version = :version")})
    public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "Customer_ID")
    private Long customerID;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 64)
    @Column(name = "Customer_Name")
    private String customerName;
    @Version
    @Basic(optional = false)
    @NotNull
    @Column(name = "Version", insertable = false ,updatable = true)
    private int version;

    public Customer() {
    }
    
    public Customer(Long customerID) {
        this.customerID = customerID;
    }
    
    public Customer(Long customerID, String customerName, int version) {
        this.customerID = customerID;
        this.customerName = customerName;
        this.version = version;
    }
    
    public Long getCustomerID() {
        return customerID;
    }
    
    public void setCustomerID(Long customerID) {
        this.customerID = customerID;
    }
    
    public String getCustomerName() {
        return customerName;
    }
    
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
    
    public int getVersion() {
        return version;
    }
    
    public void setVersion(int version) {
        this.version = version;
    }
    
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (customerID != null ? customerID.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 Customer)) {
            return false;
        }
        Customer other = (Customer) object;
        if ((this.customerID == null && other.customerID != null) || (this.customerID != null && !this.customerID.equals(other.customerID))) {
            return false;
        }
        return true;
    }
    
    @Override
    public String toString() {
        return "com.test.jpa.Customer[ customerID=" + customerID + " ]";
    }
    

    }
    ~~~~~~~~~~~~~~~~~

     
  • Kay Wrobel

    Kay Wrobel - 2013-11-11

    Hi Neil. How do you propose this should be handled? Skip the @Version field or display it, similar to Schifazl's read-only fields?

     
  • Kay Wrobel

    Kay Wrobel - 2013-11-11

    Neil and Schifazl:

    Could you two please do me a favor and check out the source code from GIT and test the new functionality? I added capability for detecting version and read-only fields. I also added new templates that handle view and edit of an individual field so I can reuse the view-related template inside Edit.xhtml. Please make sure you don't have any custom-edited templates laying around in your home directories (usually under $HOME/.netbeans/7.4/config/Templates).

    I am looking forward to your feedback.

    Neil: I had tried to replicate a @Version field in my entity and ran into issues editing (not creating) records. I don't have much experience with version fields and depend on your expertise on that.

     
  • Anonymous

    Anonymous - 2013-11-11

    Hello Kay!
    Thank you again for your quick support! I'll try tomorrow!

     
  • Neil Franken

    Neil Franken - 2013-11-11

    Kay

    The error you are getting is due to the fact that when you edit the record the version number in the grid does not get updated. So when you edit it a second time the old version number gets posted thus throwing a optimistic locking exception like the following:

    Caused by: Exception [EclipseLink-5010] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.OptimisticLockException
    Exception Description: The object [com.test.jpa.Customer[ customerID=1 ]] cannot be merged because it has changed or been deleted since it was last read. 
    Class> com.test.jpa.Customer
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    This is due to the list being loaded before the edit is made and not refreshed after the edit is made. Your list is created in the AbstractController in the getItems method.
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     public List<T> getItems() {
            if (items == null) { 
                items = this.ejbFacade.findAll();
            }
            return items;
        }
    

    Since this method is called before the edit the list is not null and thus does not get refreshed. Here is the bit you need to understand. Since we are not editing the version column but only the name of the customer the new name is placed into the grid as we are modifying the object in the list. However the version number is incremented on the back end and the list does not read from the database again. Thus while the name of the customer changes on the grid list it does not change the version number as we have not changed it.

    I have a workaround but it is not pretty. In the persist method of the AbstractController when the list gets edited set the list to null and then this will force the list to be reloaded from the database. See code below:

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    private void persist(PersistAction persistAction, String successMessage) {
    if (selected != null) {
    this.setEmbeddableKeys();
    try {
    if (persistAction != PersistAction.DELETE) {
    this.ejbFacade.edit(selected);
    /
    Forcing the items to be null here ensures that we
    reload the list from the database. This will read the
    new version number from the database and ensure optimistic locking
    works.
    /

                    this.items=null;
                } else {
                    this.ejbFacade.remove(selected);
                }
                JsfUtil.addSuccessMessage(successMessage);
            } catch (EJBException ex) {
                String msg = "";
                Throwable cause = JsfUtil.getRootCause(ex.getCause());
                if (cause != null) {
                    msg = cause.getLocalizedMessage();
                }
                if (msg.length() > 0) {
                    JsfUtil.addErrorMessage(msg);
                } else {
                    JsfUtil.addErrorMessage(ex, ResourceBundle.getBundle("/MyBundle").getString("PersistenceErrorOccured"));
                }
            } catch (Exception ex) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                JsfUtil.addErrorMessage(ex, ResourceBundle.getBundle("/MyBundle").getString("PersistenceErrorOccured"));
            }
        }
    }
    

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    On a side note I have found a generic way to implement lazy loading of all datatables. If you want I can setup a example project and send it to you so you can study it and maybe contribute the code to the project. This will then by default lazy load all of the tables and yes filtering and sorting still works.

     
    • Kay Wrobel

      Kay Wrobel - 2013-11-26

      Hi Neil. I'm testing this whole version situation again. I see what you're doing, but there might be a more elegant solution. One thing I'm investigating is the AbstractFacade class. The edit method does not return anything and, unfortunately, that code is generated by a piece of code that is not available to me. But, I've modified it for testing purposes. So here is the change I've made to AbstractFacade in my test project:

      public T edit(T entity) {
          return getEntityManager().merge(entity);
      }
      

      And in the AbstractController, I've changed the persist method as follows:

      private void persist(PersistAction persistAction, String successMessage) {
          if (selected != null) {
              this.setEmbeddableKeys();
              try {
                  if (persistAction != PersistAction.DELETE) {
                      selected = this.ejbFacade.edit(selected);
                  } else {
                      this.ejbFacade.remove(selected);
                  }
                  JsfUtil.addSuccessMessage(successMessage);
              } catch (EJBException ex) {
      .
      .
      .
      }
      

      I've set the debugger to stop inside the facade's edit method to see what gets returned from the back-end. And on the first go around when a new entity gets created, I get an object that has version set to 1. However, when I dig inside the database for that record, version is actually NULL. Do you have an idea of what is going on?

      I've created a Github for this project. Could you please take a look at it, Neil?

       
    • Kay Wrobel

      Kay Wrobel - 2013-11-26

      Neil, I looked at the entity class in my project, and removed the following line from the version field:

      @Column(insertable = false, updatable = true)
      

      That change took care of managing the version number properly. JPA now inserts 1 when the object gets created. And editing the entity also works like a charm now. I have not tested the change with the standard facade, the one that doesn't return the merged entity back to the controller. I will try that next.

      In the mean time, I have updated the test project on Github to reflect the change.

       
  • Kay Wrobel

    Kay Wrobel - 2013-11-11

    Hm. That kind of begs the question if an AJAX-ified GUI is even right for when you're dealing with version fields. So, I take it that what is really lacking in my design is the refresh of entities before editing. Is that correct? So maybe, just the entity itself should get refreshed prior to the edit, not the whole list? Just thinking out loud with my limited knowledge.

    Regarding lazy loading and a generic approach, I am very interested in seeing a solution for that since a few users have pinged me regarding that and I had brushed it off as not really possible with the ueber-generic facade we have here. So fire away. Maybe you can ppost your solution on Github.

     
  • Anonymous

    Anonymous - 2013-11-12

    Hi Neil Franken,

           I am looking for lazy loading datatable sample code with primefaces crud generator, please share your code.
    
     
  • Neil Franken

    Neil Franken - 2013-11-12

    Kay

    I just need to clean up the code slightly. It took me a solid two weeks of googling the problem and trying different things to get the generic method working. I used it in a project with over 100 tables which are very large so needed the lazy loading. At this point the code just needs a little refactoring and more comments to get it readable.

    Will have it done over the weekend and send probably blog about it.

     
    • Kay Wrobel

      Kay Wrobel - 2013-11-26

      Hi Neil. How are you doing on this topic?

       
  • Kay Wrobel

    Kay Wrobel - 2013-11-12

    Thank you, Neil.

     
  • schifazl

    schifazl - 2013-11-14

    I'm back! Sorry for the delay, I was quite busy.

    I just tried the new version. Editing and creating entities works nice, but after editing is done, the readonly fields aren't refreshed (the other fields are correctly refreshed), so I have to refresh the entire page in order to see the new value

     
  • Kay Wrobel

    Kay Wrobel - 2013-11-14

    Hm. So after you save your edit, the read-only field still contains the same content?

     
  • schifazl

    schifazl - 2013-11-14

    The field in the database gets normally updated, it's just a display issue

     
    • Kay Wrobel

      Kay Wrobel - 2013-11-26

      Hi Schifazl. Could you please have a look at the change I have made to the AbstractFacade and AbstractController classes? See discussion with Neil above and a link to a test project. It seems that with that change, the read-only field also gets updated properly. It is something you would have to do manually after generating the code.

       
  • Neil Franken

    Neil Franken - 2013-11-27

    Kay

    I apologize for the delay in getting code to you on the Lazy Data loading I am currently travelling between cities and have been in hotels without my code available. Will be home this weekend and send you what I have done.

     
    • Anonymous

      Anonymous - 2014-01-08

      Hi Neil Franken,

              I am looking for lazy loading data table sample code with primefaces crud generator, If possible can you please share your code.
      
       
1 2 > >> (Page 1 of 2)

Anonymous
Anonymous

Add attachments
Cancel