我正在使用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;
}
答案 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时遇到的问题与以下事件序列有关:
formBackingObject
formBackingObject
formBackingObject
doSubmitAction
时,Thingie
支持对象的同一实例将作为命令传递此时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或控制器代码。
感谢大家的回答。它帮助我缩小了正在发生的事情。