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

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

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

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

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

Name:
Scheme:
Url:
Enabled:

控制器里面有这个,

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

使用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字段设置为仅包含表单中的字段,并排除主键,否则最终会变得混乱!

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

  1. formBackingObject打开一个Hibernate会话(让我们称之为会话A)
  2. 使用会话A,您可以在formBackingObject加载Thingie对象
  3. 作为formBackingObject的结果返回Thingie对象
  4. 当你返回Thingie对象时,会话A被关闭,但Thingie仍然与它相关联
  5. 调用doSubmitActionThingie支持对象的同一实例将作为命令传递
  6. 打开一个新的Hibernate会话(称为会话B)
  7. 您尝试使用会话B保存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 } 

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

  Name: 
Scheme:
Url:
Enabled:

现在定义表单控制器,它将填充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); } } 

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

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

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