Spring MVC,从请求生成表单支持对象?

时间:2009-03-30 15:50:21

标签: java hibernate spring-mvc

我正在使用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应用程序工作,不断抛出上面所见的Stale Object State Exception。

如果熟悉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 个答案:

答案 0 :(得分:6)

使用Spring MVC + hibernate的一个主要缺点是,自然的方法是使用hibernate域对象作为表单的后备对象。 Spring将根据DEFAULT的名称绑定请求中的任何内容。这无意中包括ID或名称(通常是主键)或其他正在设置的hibernate托管属性。这也使你容易形成注射。

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

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

并且明确地将ALLOWED字段设置为仅包含表单中的字段,并排除主键,否则最终会出现问题!

答案 1 :(得分:5)

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

  1. formBackingObject
  2. 中打开一个Hibernate会话(让我们称之为会话A)
  3. 使用会话A,您可以在formBackingObject
  4. 中加载Thingie对象
  5. 您返回Thingie对象作为formBackingObject
  6. 的结果
  7. 当您返回Thingie对象时,会话A已关闭,但Thingie仍然与之关联
  8. 调用doSubmitAction时,Thingie支持对象的同一实例将作为命令传递
  9. 打开一个新的Hibernate会话(称之为会话B)
  10. 您尝试使用会话B
  11. 保存Thingie对象(最初使用会话A打开)

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

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

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

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