You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

问题分析与解决方案

核心原因

  1. 会话生命周期与懒加载冲突:首次访问时,Refund的懒加载属性openingMotivation/closureMotivation未被初始化,但JSP中直接访问了openingMotivation.code,此时Hibernate会话可能已关闭,导致尝试初始化瞬态实例;刷新后,会话重新绑定,或者数据已被缓存,所以正常。
  2. ID生成策略未明确:Motivation的主键code是手动维护的字符串类型,Hibernate未正确识别其主键生成策略,导致保存时误判为需要手动赋值(但实际数据库中已有数据)。
  3. JSP直接调用DAO的风险:JSP脚本<%=MotivationDAO.getMotivationsByType("A")%>会在视图渲染时复用或创建会话,可能和当前请求的会话产生冲突,导致实体状态混乱。

修复方案

  • 保持会话在请求周期内打开:使用OpenSessionInViewFilter过滤器,确保JSP渲染时Hibernate会话仍处于打开状态,避免懒加载属性在会话关闭后被访问。
  • 显式指定Motivation主键策略:在Motivation类或hbm.xml中明确主键生成策略为assigned(因为code是手动维护的):
    @Id
    @GeneratedValue(strategy = GenerationType.ASSIGNED)
    private String code;
    
    或在hbm.xml中配置:
    <id name="code" type="string">
        <column name="CODE"/>
        <generator class="assigned"/>
    </id>
    
  • Controller层提前查询数据:避免JSP直接调用DAO,在Controller中先查询Motivation列表并放入Model,JSP直接从Model获取数据:
    // Controller代码示例
    List<Motivation> openingMotivations = MotivationDAO.getMotivationsByType("A");
    model.addAttribute("openingMotivations", openingMotivations);
    
    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="${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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 03:05:31