org.hibernate.LazyInitializationException:无法初始化代理 [com.sampleapp.model.User] - 否 Session

org.hibernate.LazyInitializationException: could not initialize proxy [com.sampleapp.model.User] - no Session

我正在尝试使用 Primefaces+JSF+Spring Boot 做一个示例用户 CRUD 页面。 在页面上,我启用了 LazyLoading table。我的用户 object 没有 1-to-N 或 N-to-1 字段, 所有都是原始字段,到达时不需要数据库访问或初始化。 (所以 FetchType.EAGER 无济于事)

当尝试在 pop-up 上显示 UserList table 中的用户时,出现以下异常:

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)

为什么 JSF 组件会尝试从数据库中更新 object 以获取 1 对 1 的 STRING 用户名字段?

我在谷歌上搜索了很多,看到一些修复建议可能在 session 期间开放:

但这些都是 anti-patterns 治标不治本,仍然会损害性能并且无法使用框架的设计目的。这是错误还是设计问题?

这个问题有好的解决办法吗?

@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);
    }
}

在 BaseRepository class 中延迟加载(count 和 findAll)时使用的方法被所有存储库扩展:

@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;
    }
}

在这个问题上,LazyLoading 似乎是在转移注意力。问题在于如何调用对话框。 jsf 片段包括数据表上的行选择,但对话框是由按钮调用的。两者可能不同步。相反,在调用对话框时设置当前行选择,如下所示:

<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>

此问题导致 Hibernate“无会话”错误的确切原因仍然是个谜。