Java Spring MVC,从请求生成表单支持对象?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/697778/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring MVC, generating a form backing object from a request?
提问by James McMahon
I am using Spring MVC 2.5, and I am trying to get a JSTL form object to load from a GET request. I have Hibernate POJOs as my backing objects.
我正在使用 Spring MVC 2.5,并且我正在尝试从 GET 请求中获取一个 JSTL 表单对象。我有 Hibernate POJO 作为我的支持对象。
There is one page directing to another page with a class id (row primary key) in the request. The request looks like "newpage.htm?name=RowId". This is going into a page with a form backing object,
在请求中有一个页面指向另一个具有类 ID(行主键)的页面。该请求看起来像“newpage.htm?name=RowId”。这将进入一个带有表单支持对象的页面,
The newpage above, loads the fields of the object into editable fields, populated with the existing values of the row. The idea is, that you should be able to edit these fields and then persist them back into the database.
上面的新页面将对象的字段加载到可编辑字段中,并填充了该行的现有值。这个想法是,您应该能够编辑这些字段,然后将它们保存回数据库。
The view of this page looks something like this
这个页面的视图看起来像这样
<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>
The controller has this in it,
控制器里面有这个,
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);
}
}
As you can see from the commented code, I've tried manually setting the object id (name is this case) from the request. However Hibernate complains about the object being desynched when I try and persist the data in the form.
正如您从注释代码中看到的那样,我尝试从请求中手动设置对象 id(名称就是这种情况)。但是,当我尝试将数据保存在表单中时,Hibernate 会抱怨对象不同步。
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
This error seems to do something to the entire session, which stops working for my entire web application, continually throwing the Stale Object State Exception seen above.
这个错误似乎对整个会话有影响,它停止为我的整个 web 应用程序工作,不断抛出上面看到的陈旧对象状态异常。
If anyone familiar with Spring MVC can help me with this or suggest a workaround, I would really appreciate it.
如果任何熟悉 Spring MVC 的人可以帮助我解决这个问题或提出解决方法,我将不胜感激。
EDIT:
Session factory code.
编辑:
会话工厂代码。
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;
}
采纳答案by Justin
One of the major flaws with using Spring MVC + hibernate is that the natural approach is to use the hibernate domain object as the backing object for the form. Spring will bind anything in the request based on name by DEFAULT. This inadvertently includes things like ID or name (usually the primary key) or other hibernate managed properties being set. This also makes you vulnerable to form injection.
使用 Spring MVC + hibernate 的主要缺陷之一是自然的方法是使用 hibernate 域对象作为表单的支持对象。Spring 将根据 DEFAULT 的名称绑定请求中的任何内容。这会无意中包括诸如 ID 或名称(通常是主键)或其他被设置的休眠托管属性之类的东西。这也使您容易形成注射。
In order to be secure under this scenario you must use something like:
为了在这种情况下安全,您必须使用以下内容:
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
throws Exception {
String[] allowedFields = {"name", "birthday"}
binder.setAllowedFields(allowedFields);
}
and EXPLICITLY set the ALLOWED fields to only those in your form, and exclude the primary key or you will end up with a mess!!!
并明确地将 ALLOWED 字段设置为仅在您的表单中的字段,并排除主键,否则您最终会一团糟!!!
回答by Elie
Your issue may be related to Detached Objects. Because your DAO has been modified outside a Hibernate session, you need to reattach the object to the Hibernate session before saving. You can do this either by explicitly bringing the object into the session before saving using Merge() or update(). Experiment with both, and read the documentation for those actions, as they have different effects depending on the structure of your data objects.
您的问题可能与分离的对象有关。因为您的 DAO 已在 Hibernate 会话之外被修改,所以您需要在保存之前将该对象重新附加到 Hibernate 会话。您可以通过在使用 Merge() 或 update() 保存之前将对象显式引入会话来完成此操作。对两者进行试验,并阅读这些操作的文档,因为它们会根据数据对象的结构产生不同的效果。
回答by William Brendel
To answer your immediate question, the problem you are having with Hibernate has to do with the following sequence of events:
为了回答您的直接问题,您在使用 Hibernate 时遇到的问题与以下事件序列有关:
- One Hibernate session is opened (let's call it session A) in
formBackingObject
- Using session A, you load a Thingie object in
formBackingObject
- You return the Thingie object as the result of
formBackingObject
- When you return the Thingie object, session A is closed, but Thingie is still linked to it
- When
doSubmitAction
is called, the same instance of theThingie
backing object is passed as the command - A new Hibernate session (call it session B) is opened
- You attempt to save the Thingie object (originally opened with session A) using session B
- 一个 Hibernate 会话被打开(我们称之为会话 A)
formBackingObject
- 使用会话 A,您将 Thingie 对象加载到
formBackingObject
- 您返回 Thingie 对象作为结果
formBackingObject
- 当您返回 Thingie 对象时,会话 A 已关闭,但 Thingie 仍与其链接
- 当
doSubmitAction
被调用时,Thingie
作为命令传递的后备对象的相同实例 - 打开一个新的 Hibernate 会话(称为会话 B)
- 您尝试使用会话 B 保存 Thingie 对象(最初使用会话 A 打开)
Hibernate doesn't know anything about session A at this point, because it's closed, so you get an error. The good news is that you shouldn't be doing it that way, and the correct way will bypass that error completely.
此时 Hibernate 对会话 A 一无所知,因为它已关闭,因此您会收到错误消息。好消息是你不应该那样做,正确的方法将完全绕过那个错误。
The formBackingObject
method is used to populate a form's command object with data, prior to showing the form. Based on your updated question, it sounds like you are simply trying to display a form populated with information from a given database row, and update that database row when the form is submitted.
该formBackingObject
方法用于在显示表单之前用数据填充表单的命令对象。根据您更新的问题,听起来您只是想显示一个用给定数据库行中的信息填充的表单,并在提交表单时更新该数据库行。
It looks like you already have a model class for your record; I'll call that the Record
class in this answer). You also have DAO for the Record
class, which I will call RecordDao
. Finally, you need a UpdateRecordCommand
class that will be your backing object. The UpdateRecordCommand
should be defined with the following fields and setters/getters:
看起来您已经有一个模型类作为记录;我会Record
在这个答案中称之为类)。您还拥有该Record
课程的DAO ,我将称之为RecordDao
. 最后,您需要一个UpdateRecordCommand
类作为您的支持对象。在UpdateRecordCommand
应与以下字段和setter /吸气剂来定义:
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
}
Then define your form using the following code:
然后使用以下代码定义表单:
<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>
Now you define your form controller, which will populate the form in formBackingObject
and process the update request in doSubmitAction
.
现在您定义表单控制器,它将填充表单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);
}
}
回答by James McMahon
What was going on is that the ?name=rowId was somehow messing up the form post. Once I changed that to a name that didn't reflect a parameter in the object, everything worked fine. No change to the DAO or controller code necessary.
发生的事情是 ?name=rowId 不知何故弄乱了表单帖子。一旦我将其更改为不反映对象中参数的名称,一切正常。无需更改 DAO 或控制器代码。
Thanks to everyone for your answers. It helped me narrow down what was going on.
感谢大家的回答。它帮助我缩小了正在发生的事情的范围。