5

我正在使用 Spring MVC 2.5,并且试图从 GET 请求中加载一个 JSTL 表单对象。我有 Hibernate POJO 作为我的支持对象。

请求中有一个页面指向另一个具有类 ID(行主键)的页面。该请求看起来像“newpage.htm?name=RowId”。这将进入一个带有表单支持对象的页面,

上面的新页面将对象的字段加载到可编辑字段中,并填充了行的现有值。这个想法是,您应该能够编辑这些字段,然后将它们保存回数据库。

此页面的视图看起来像这样

<form:form commandName="thingie">
    <span>Name:</span>
    <span><form:input path="name" /></span>
    <br/>
    <span>Scheme:</span>
    <span><form:input path="scheme" /></span>
    <br/>
    <span>Url:</span>
    <span><form:input path="url" /></span>
    <br/>
    <span>Enabled:</span>
    <span><form:checkbox path="enabled"/></span>
    <br/>

    <input type="submit" value="Save Changes" />
</form:form>

控制器里面有这个,

public class thingieDetailController extends SimpleFormController {

    public thingieDetailController() {    
        setCommandClass(Thingie.class);
        setCommandName("thingie");
    }

    @Override
    protected Object formBackingObject(HttpServletRequest request) throws Exception {
        Thingie thingieForm = (Thingie) super.formBackingObject(request);

        //This output is always null, as the ID is not being set properly
        logger.debug("thingieForm.getName(): [" + thingieForm.getName() + "]");
        //thingieForm.setName(request.getParameter("name"));
        SimpleDAO.loadThingie(thingieForm);

        return thingieForm;
    }

    @Override
    protected void doSubmitAction(Object command) throws Exception {            
        Thingie thingie = (Thingie) command;
        SimpleDAO.saveThingie(thingie);
    }
}

正如您从注释代码中看到的那样,我尝试从请求中手动设置对象 ID(本例为名称)。然而,当我尝试将数据保存在表单中时,Hibernate 抱怨对象被不同步。

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

这个错误似乎对整个会话有影响,它停止了对我的整个 Web 应用程序的工作,不断地抛出上面看到的过时对象状态异常。

如果熟悉 Spring MVC 的人可以帮助我或提出解决方法,我将不胜感激。

编辑:
会话工厂代码。

private static final SessionFactory sessionFactory;
private static final Configuration configuration = new Configuration().configure();

static {
    try {
        // Create the SessionFactory from standard (hibernate.cfg.xml) 
        // config file.
        sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    } catch (Throwable ex) {
        // Log the exception. 
        System.err.println("Initial SessionFactory creation failed." + ex);
        throw new ExceptionInInitializerError(ex);
    }
}

public static SessionFactory getSessionFactory() {
    return sessionFactory;
}
4

4 回答 4

6

使用 Spring MVC + hibernate 的主要缺陷之一是自然的方法是使用 hibernate 域对象作为表单的支持对象。Spring 将根据默认名称绑定请求中的任何内容。这无意中包含了诸如 ID 或名称(通常是主键)或其他正在设置的休眠托管属性之类的内容。这也使您容易受到形式注入。

为了在这种情况下安全,您必须使用以下内容:

protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) 
throws Exception {
 String[] allowedFields = {"name", "birthday"}
 binder.setAllowedFields(allowedFields);
}

并明确将允许的字段设置为仅表单中的字段,并排除主键,否则最终会一团糟!!!

于 2009-08-17T16:39:47.213 回答
5

要回答您的直接问题,您遇到的 Hibernate 问题与以下事件序列有关:

  1. 一个 Hibernate 会话在formBackingObject
  2. 使用会话 A,您将 Thingie 对象加载到formBackingObject
  3. 您返回 Thingie 对象作为结果formBackingObject
  4. 当您返回 Thingie 对象时,会话 A 已关闭,但 Thingie 仍与其链接
  5. doSubmitAction被调用时,支持对象的相同实例Thingie作为命令传递
  6. 打开一个新的 Hibernate 会话(称为会话 B)
  7. 您尝试使用会话 B 保存 Thingie 对象(最初使用会话 A 打开)

Hibernate 在这一点上对会话 A 一无所知,因为它已关闭,因此您会收到错误消息。好消息是你不应该那样做,正确的方法会完全绕过这个错误。

formBackingObject方法用于在显示表单之前用数据填充表单的命令对象。根据您更新的问题,听起来您只是想显示一个填充了来自给定数据库行的信息的表单,并在提交表单时更新该数据库行。

看起来您已经有一个模型类供您记录;Record我将在此答案中称其为班级)。您还有该Record课程的 DAO,我将其称为RecordDao. 最后,您需要一个UpdateRecordCommand类作为您的支持对象。UpdateRecordCommand应该使用以下字段和 setter/getter 定义:

public class UpdateRecordCommand {
  // Row ID of the record we want to update
  private int rowId;
  // New name
  private int String name;
  // New scheme
  private int String scheme;
  // New URL
  private int String url;
  // New enabled flag
  private int boolean enabled;

  // Getters and setters left out for brevity
}

然后使用以下代码定义您的表单:

<form:form commandName="update">
  <span>Name:</span>
  <span><form:input path="name" /></span><br/>
  <span>Scheme:</span>
  <span><form:input path="scheme" /></span><br/>
  <span>Url:</span>
  <span><form:input path="url" /></span><br/>
  <span>Enabled:</span>
  <span><form:checkbox path="enabled"/></span><br/>
  <form:hidden path="rowId"/>
  <input type="submit" value="Save Changes" />
</form:form>

现在你定义了你的表单控制器,它将填充表单formBackingObject并处理更新请求doSubmitAction

public class UpdateRecordController extends SimpleFormController {

  private RecordDao recordDao;

  // Setter and getter for recordDao left out for brevity

  public UpdateRecordController() {    
      setCommandClass(UpdateRecordCommand.class);
      setCommandName("update");
  }

  @Override
  protected Object formBackingObject(HttpServletRequest request)
      throws Exception {
    // Use one of Spring's utility classes to cleanly fetch the rowId
    int rowId = ServletRequestUtils.getIntParameter(request, "rowId");

    // Load the record based on the rowId paramrter, using your DAO
    Record record = recordDao.load(rowId);

    // Populate the update command with information from the record
    UpdateRecordCommand command = new UpdateRecordCommand();

    command.setRowId(rowId);
    command.setName(record.getName());
    command.setScheme(record.getScheme());
    command.setUrl(record.getUrl());
    command.setEnabled(record.getEnabled());

    // Returning this will pre-populate the form fields
    return command;
  }

  @Override
  protected void doSubmitAction(Object command) throws Exception {
    // Load the record based on the rowId in the update command
    UpdateRecordCommand update = (UpdateRecordCommand) command;
    Record record = recordDao.load(update.getRowId());

    // Update the object we loaded from the data store
    record.setName(update.getName());
    record.setScheme(update.getScheme());
    record.setUrl(update.getUrl());
    record.setEnabled(update.setEnaled());

    // Finally, persist the data using the DAO
    recordDao.save(record);
  }
}
于 2009-03-30T15:58:49.793 回答
1

您的问题可能与分离的对象有关。因为您的 DAO 已在 Hibernate 会话之外进行了修改,所以您需要在保存之前将对象重新附加到 Hibernate 会话。您可以通过在使用 Merge() 或 update() 保存之前显式地将对象带入会话来执行此操作。对两者进行试验,并阅读这些操作的文档,因为它们会根据数据对象的结构产生不同的效果。

于 2009-03-30T15:55:26.853 回答
1

发生的事情是 ?name=rowId 以某种方式弄乱了表单帖子。一旦我将其更改为不反映对象中参数的名称,一切正常。无需更改 DAO 或控制器代码。

感谢大家的回答。它帮助我缩小了正在发生的事情的范围。

于 2009-03-30T21:19:46.870 回答