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 期间开放:
- “在视图中打开 Session”
- 设置“spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true”
但这些都是 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“无会话”错误的确切原因仍然是个谜。
我正在尝试使用 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 期间开放:
- “在视图中打开 Session”
- 设置“spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true”
但这些都是 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“无会话”错误的确切原因仍然是个谜。