Hibernate仅首次执行抛异常:退款实体关联Motivation类问题求助
问题描述
我有一个名为Refund的实体,包含两个指向同一Motivation实体的外键。Refund类通过@ManyToOne注解关联Motivation,而Motivation类因继承关系由hbm.xml自动绑定,Oracle数据库中对应表已存在且有数据。
我需要在JSP中根据type字段过滤Motivation数据填充下拉框,通过MotivationDAO的getMotivationsByType方法实现查询。但部分Refund实体首次访问时,该方法抛出object references an unsaved transient instance - save the transient instance before flushing异常;调整后又出现ids for this class must be manually assigned before calling save()异常,且无法添加自增ID列。
奇怪的是,刷新对应JSP后功能完全正常。我试过多种修复方案(比如修改外键属性、重构下拉框语法等),但都会导致保存操作出现问题。我搞不懂:为什么修改无关代码能解决问题?为什么第二次执行就正常?
相关代码
Refund.java
// 其他列 @ManyToOne(targetEntity=Motivation.class, optional=true, fetch=FetchType.LAZY) @JoinColumn(name="opening_motivation", referencedColumnName="code") @ForeignKey(name="REFUND_OPENING_MOTIVATION_FK") private Motivation openingMotivation; @ManyToOne(targetEntity=Motivation.class, optional=true, fetch=FetchType.LAZY) @JoinColumn(name="closure_motivation", referencedColumnName="code") @ForeignKey(name="REFUND_CLOSURE_MOTIVATION_FK") private Motivation closureMotivation; // getter和setter
Motivation.java
private String code; private String type; private String description; // getter和setter
MotivationDAO.java
public static List<Motivation> getMotivationsByType(String type) throws DatabaseException { Criteria criteria = null; try { Session session = HibernateUtil.currentSession(); criteria = session.createCriteria(Motivation.class); criteria.add(Restrictions.eq("type", type)); return (List<Motivation>) criteria.list(); } catch (HibernateException e) { throw new DatabaseException(e.getMessage()); }catch (Exception e) { System.out.println("getMotivationList: " + e.getMessage()); throw new DatabaseException("an error occured"); } }
JSP代码
<form:select cssClass="comboBox" path="pageAction.currentPL.entity.openingMotivation.code" id="openingCombo" disabled="true"> <form:option value=""></form:option> <c:forEach var="openingMotivation" items='<%=MotivationDAO.getMotivationsByType("A")%>'> <form:option value="${openingMotivation.code}">${openingMotivation.code} - ${openingMotivation.description}</form:option> </c:forEach> </form:select>
问题分析与解决方案
核心原因
- 会话生命周期与懒加载冲突:首次访问时,
Refund的懒加载属性openingMotivation/closureMotivation未被初始化,但JSP中直接访问了openingMotivation.code,此时Hibernate会话可能已关闭,导致尝试初始化瞬态实例;刷新后,会话重新绑定,或者数据已被缓存,所以正常。 - ID生成策略未明确:
Motivation的主键code是手动维护的字符串类型,Hibernate未正确识别其主键生成策略,导致保存时误判为需要手动赋值(但实际数据库中已有数据)。 - JSP直接调用DAO的风险:JSP脚本
<%=MotivationDAO.getMotivationsByType("A")%>会在视图渲染时复用或创建会话,可能和当前请求的会话产生冲突,导致实体状态混乱。
修复方案
- 保持会话在请求周期内打开:使用
OpenSessionInViewFilter过滤器,确保JSP渲染时Hibernate会话仍处于打开状态,避免懒加载属性在会话关闭后被访问。 - 显式指定Motivation主键策略:在
Motivation类或hbm.xml中明确主键生成策略为assigned(因为code是手动维护的):
或在hbm.xml中配置:@Id @GeneratedValue(strategy = GenerationType.ASSIGNED) private String code;<id name="code" type="string"> <column name="CODE"/> <generator class="assigned"/> </id> - Controller层提前查询数据:避免JSP直接调用DAO,在Controller中先查询
Motivation列表并放入Model,JSP直接从Model获取数据:
JSP修改为:// Controller代码示例 List<Motivation> openingMotivations = MotivationDAO.getMotivationsByType("A"); model.addAttribute("openingMotivations", openingMotivations);<form:select cssClass="comboBox" path="pageAction.currentPL.entity.openingMotivation.code" id="openingCombo" disabled="true"> <form:option value=""></form:option> <c:forEach var="openingMotivation" items="${openingMotivations}"> <form:option value="${openingMotivation.code}">${openingMotivation.code} - ${openingMotivation.description}</form:option> </c:forEach> </form:select> - 规范实体状态:确保
Refund实体绑定到表单前,其openingMotivation/closureMotivation要么是从数据库加载的持久化实例,要么显式设置为null,避免出现未初始化的代理对象。
内容的提问来源于stack exchange,提问作者Marco Frag Delle Monache
相关产品推荐
相关产品推荐

