0

I am trying to do a sample User CRUD page with Primefaces+JSF+Spring Boot. On the page, I have a LazyLoading enabled table. My User object has no 1-to-N or N-to-1 fields, all are primitive fields that does not need database access or initialization upon reaching. (So FetchType.EAGER won't help)

When trying to show a User from the UserList table on a pop-up, getting the exception below:

ERROR j.e.r.webcontainer.jsf.context - javax.faces.component.UpdateModelException: 
javax.el.ELException: /ui/admin/users.xhtml @176,64 value="#{userBean.selectedUser.username}": 
org.hibernate.LazyInitializationException: could not initialize proxy [com.sampleapp.model.User#121] - no Session
    at javax.faces.component.UIInput.updateModel(UIInput.java:868)
    at javax.faces.component.UIInput.processUpdates(UIInput.java:751)
    at javax.faces.component.UIComponentBase.processUpdates(UIComponentBase.java:1291)
    at javax.faces.component.UIComponentBase.processUpdates(UIComponentBase.java:1291)
    at org.primefaces.component.outputpanel.OutputPanel.processUpdates(OutputPanel.java:98)
    at javax.faces.component.UIComponentBase.processUpdates(UIComponentBase.java:1291)
    at org.primefaces.component.dialog.Dialog.processUpdates(Dialog.java:161)
    at javax.faces.component.UIForm.processUpdates(UIForm.java:281)
    at javax.faces.component.UIComponentBase.processUpdates(UIComponentBase.java:1291)

Why does JSF component try to update the object from database for a STRING username field which is 1-to-1?

I have googled a lot and saw some fix proposals likely having an open session during:

  • "Open Session in View"
  • Setting "spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true"

But those are all anti-patterns curing just symptoms, still damaging performance and not abling to use the framework as it is designed for. Is this a bug or a design issue?

Is there a proper solution to this problem?

@Entity
@Table(name = "APP_USER")
public class User extends BaseModel {

    private String    username;
    private String    password;
    
    private String    firstName;
    private String    lastName;
    private Role      role;
    private UserState state;
    private String    email;
}

users.xhtml

    <h:form id="userform">
        <p:panel id="mainpanel" >
            <p:dataTable id="usertbl" var="user" value="#{userBean.userModel}"
                paginator="true" rows="#{userBean.userModel.pageSize}"
                selectionMode="single" selection="#{userBean.selectedUser}"
                paginatorPosition="bottom" lazy="true"
                emptyMessage="no data" >

                <p:column sortBy="#{user.username}" filterBy="#{user.username}">
                    <f:facet name="header">
                        <h:outputText value="Username:" />
                    </f:facet>
                    <h:outputText value="#{user.username}" />
                </p:column>

                .
                .

                <p:column style="width:4%">
                    <p:commandButton update=":userform:editUserPopup" 
                        oncomplete="PF('editUserPopupWgt').show()" icon="ui-icon-search" title="View">
                    </p:commandButton>
                </p:column>
                
                <p:column style="width:4%">
                    <p:commandButton update=":userform:deleteUserDialog" 
                        oncomplete="PF('deleteUserDialogWgt').show()" icon="ui-icon-close" title="View">
                    </p:commandButton>
                </p:column>
           </p:dataTable>
           .
           .

UserBean.java

@Component(value = "userBean")
@Scope("session")
public class UserBean implements Serializable {

    private UserLazyDataModel userModel;

    private User selectedUser;

    @Autowired
    UserRepository userRepository;

    @PostConstruct
    public void initModel() {
        userModel = new UserLazyDataModel(userRepository);
    }

    public void saveSelectedUser() {

        userRepository.save(selectedUser);

        userModel = new UserLazyDataModel(userRepository);
    }

     public void deleteUser() {

        userRepository.delete(selectedUser);

        userModel = new UserLazyDataModel(userRepository);
    }
}

UserLazyLoadModel.java

@Log
@Data
public class UserLazyDataModel extends LazyDataModel<User> {


    private UserRepository userRepository;

    public UserLazyDataModel() {
        super.setPageSize(Configurations.PAGE_SIZE);
    }

    public UserLazyDataModel(UserRepository userRepository) {
        super.setPageSize(Configurations.PAGE_SIZE);
        this.userRepository = userRepository;
    }

    @Override
    public List<User> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, FilterMeta> filters) {

        if (sortField == null) {
            sortField = "createTime";
            sortOrder = SortOrder.DESCENDING;
        }

        List<User> users = new ArrayList<User>();
        try {
            
            super.setRowCount(userRepository.countEntities(-1, -1, null, sortOrder.name(), filters, null, null).intValue());
            users = userRepository.findEntities(first, pageSize, sortField, sortOrder.name(), filters, null, null);
        } catch (Exception e) {
            log.severe(e.getMessage());
        }

        return users;
    }

    @Override
    public String getRowKey(User user) {
        return user.getId() + "";
    }

    @Override
    public User getRowData(String rowKey) {
        try {
            return userRepository.getOne(Long.parseLong(rowKey));
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void setPageSize(int pageSize) {
        super.setPageSize(pageSize);
    }
}

Methods used at lazyloading (count & findAll) at BaseRepository class extended by all repositories:

@Data
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
  
    private EntityManager entityManager;
  

    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    // ...
  
    @Override
    public Long countEntities(int first, int pageSize, String sortField, String sortOrder, Map<String, FilterMeta> filters, Map<String, String> aliases, Criterion extraCriterion)
            throws SecurityException, NoSuchFieldException {
        
        Long count = 0L;
        

        Session session = entityManager.unwrap(Session.class);
        
        
        try {
            Criteria crit = session.createCriteria(getDomainClass());
    
            crit = prepareCriteria(first, pageSize, sortField, sortOrder, filters, aliases, extraCriterion, crit);

            crit.setProjection(Projections.rowCount());
        
            if (crit.list() != null) {
                count = (Long) crit.list().get(0);
            }
            
        } catch (Exception e) {
            e.printStackTrace();    
        } finally {
                    
            if(session != null){
                session.close();
            }
        }

        return count;
    }

    @Override
    public List<T> findEntities(int first, int pageSize, String sortField, String sortOrder, Map<String, FilterMeta> filters, Map<String, String> aliases, Criterion extraCriterion)
            throws SecurityException, NoSuchFieldException {
        
        List<T> list = null;
        
        Session session = entityManager.unwrap(Session.class);
        
        try {

            Criteria crit = session.createCriteria(getDomainClass());
    
            crit = prepareCriteria(first, pageSize, sortField, sortOrder, filters, aliases, extraCriterion, crit);
    
            crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            list = crit.list();
        } catch (Exception e) {
            e.printStackTrace();    
        } finally {
                    
            if(session != null){
                session.close();
            }
        }   
        
        return list;
    }

    private Criteria prepareCriteria(int first, int pageSize, String sortField, String sortOrder, Map<String, FilterMeta> filters, Map<String, String> aliases, Criterion extraCriterion, Criteria crit)
            throws NoSuchFieldException {
        if (aliases != null && !aliases.isEmpty()) {
            Iterator<Entry<String, String>> iterator = aliases.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<String, String> entry = iterator.next();
                crit.createAlias(entry.getKey(), entry.getValue(), Criteria.LEFT_JOIN);
            }
        }

        if (extraCriterion != null) {
            crit.add(extraCriterion);
        }

        if (sortField != null && !sortField.isEmpty()) {
            if (!sortOrder.equalsIgnoreCase("UNSORTED")) {
                if (sortOrder.equalsIgnoreCase("ASCENDING")) {
                    crit = crit.addOrder(Order.asc(sortField));
                } else {
                    crit = crit.addOrder(Order.desc(sortField));
                }
            }
        }

        if (filters != null && !filters.isEmpty()) {
            Iterator<Entry<String, FilterMeta>> iterator = filters.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<String, FilterMeta> entry = iterator.next();
                Class<?> type = getDomainClass().getDeclaredField(entry.getKey()).getType();

                if(entry.getValue().getFilterValue() == null) {
                    continue;
                }
                
                try {
                    if (BigDecimal.class.isAssignableFrom(type)) {
                        crit = crit.add(Restrictions.eq(entry.getKey(), new BigDecimal(entry.getValue().getFilterValue() + "")));
                    } else if (type.isEnum() || Number.class.isAssignableFrom(type) || Double.class.isAssignableFrom(type)) {
                        crit = crit.add(Restrictions.eq(entry.getKey(), type.getDeclaredMethod("valueOf", String.class).invoke(null, entry.getValue().getFilterValue())));
                    } else if (type.getCanonicalName().indexOf(Configurations.COMPANY_JAVA_PACKAGE) != -1) {  
                      SimpleExpression idEq = Restrictions.eq(entry.getKey() + ".id", Long.parseLong(entry.getValue().getFilterValue() + ""));   
                      crit = crit.add(idEq);
                  } else {
                        crit = crit.add(Restrictions.like(entry.getKey(), String.valueOf(entry.getValue().getFilterValue()), MatchMode.START));    
                    }
                } catch (Exception ex) {
                }
            }
        }

        if (first != -1) {
            crit = crit.setFirstResult(first);
        }

        if (pageSize != -1) {
            crit = crit.setMaxResults(pageSize);
        }
        return crit;
    }
}
6
  • Do you get the same issue when lazy="false"? What is the scope of userBean? Commented May 21, 2021 at 10:23
  • Added UserBean class. It is session scoped. I did not try lazy="false" because it will disable lazy loading immediately. But as I check now, setting lazy="false" makes the data already on the table disappear, listing no data. Commented May 21, 2021 at 11:14
  • My guess is the problem is within your UserLazyDataModel implementation. No Session usually means invalid proxy. Proxys are only valid for one transaction. When trying to lazy load the second batch, it will be a different transaction. I wonder if the reference to the new starting point is somehow triggering the error - but this is only a guess. If you can get a version working without lazy loading, it will be clearer where the issue lies. Commented May 21, 2021 at 15:45
  • Added full implementation. I open session when countEntities & findEntities called with "entityManager.unwrap(Session.class);". But as stack trace tells " at org.primefaces.component.dialog.Dialog.processUpdates(Dialog.java:161)", I got exception when pressing pop-up button (editUserPopupWgt) at 2nd time on a data row on table. When I press the button 1st time, pop-up works with no exception but at 2nd time, pop-up opens with "no session" exception in logs. I don't understand why it needs to go to db to refresh & merge an object it already has with primitive attributes to show at dialog Commented May 21, 2021 at 19:34
  • That's a lot of code to go through ;-). Please try these couple of changes: delete selectionMode and selection from dataTable. Then add <f:setPropertyActionListener value="#{user}" target="#{userBean.selectedUser}"/> to the middle of the two p:commandButton . I don't think this will solve it but it will align better with PF showcase examples (see DataTable -> Selection). Commented May 21, 2021 at 19:50

1 Answer 1

0

It seems that LazyLoading was a red herring on this question. The issue is with how the dialogs are invoked. The jsf snippet includes line selection on the dataTable, but the dialogs are invoked by buttons. The two can be out of sync. Instead, set the current row selection when the dialog is invoked, as follows:

<p:dataTable id="usertbl" var="user" value="#{userBean.userModel}" lazy="true" 
             paginator="true" rows="#{userBean.userModel.pageSize}"
             paginatorPosition="bottom" emptyMessage="no data" >
    .
    .
    <p:column style="width:4%">
        <p:commandButton update=":userform:editUserPopup" 
                         oncomplete="PF('editUserPopupWgt').show()" 
                         icon="ui-icon-search" title="View">
            <f:setPropertyActionListener value="#{user}" 
                                         target="#{userBean.selectedUser}"/>
        </p:commandButton>
    </p:column>

The exact reason why this issue resulted in a Hibernate "no Session" error remains a mystery.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.