Spring MVC复杂对象数据绑定 [英] Spring MVC complex object data binding

查看:99
本文介绍了Spring MVC复杂对象数据绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我仍在为Spring MVC奋斗,这应该是一个相当简单的问题,但似乎在Spring MVC文档中很少记录。

I am still struggling with Spring MVC with what should be a fairly straightforward problem but what seems to be sparsly documented in Spring MVC documentation.

我的项目使用的是Spring MVC和Thymeleaf表示视图,但是视图渲染引擎与该问题并不真正相关。

My project uses Spring MVC and Thymeleaf for the views, but the view rendering engine is not really relevant to the problem.

我的应用程序围绕一个Activity类进行建模,该Activity类对(室内或室外)模型进行建模由成员组织的活动,其他成员可以订阅。一个Activity有一个Category字段和Region字段,它们是下拉字段,由Hibernate建模为包含ID和描述字段的数据库查找表的多对一实体。

My application is centered around an Activity class which models an (indoor or outdoor) activity which is organized by a member and where fellow members can subscribe to. An Activity has, among others, a Category field and a Region field, which are dropdown fields which are modeled by Hibernate as many-to-one entities to DB lookup tables which contain an id and description field.

Activity实体类的代码如下,省略了不相关的字段以缩短代码:

The code for the Activity entity class is as follows, the non relevant fields are omitted to shorten the code:

package nl.drsklaus.activiteitensite.model;

//imports

@Entity
@Table(name="activity")
public class Activity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Integer id;

@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="organizer_id")
private Member organizer;

@Size(min=5, max=50)
@Column(name = "title", nullable = false)
private String title;

@Size(min=5, max=500)
@Column(name = "description", nullable = false)
private String description;

@ManyToOne
@JoinColumn(name="category_id")
private ActivityCategory category;

@ManyToOne
@JoinColumn(name="region_id")
private ActivityRegion region;


@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name="member_activity_subscription", 
           joinColumns = {@JoinColumn(name="activity_id")}, 
           inverseJoinColumns={@JoinColumn(name="member_id")})
private List<Member> participants = new ArrayList<Member>();

//getters and setters   

@Override
public int hashCode() {
  ...
}

@Override
public boolean equals(Object obj) {
  ...
}
}

在视图中,用户应该能够从选择框中选择区域和类别。选项是在类级别上使用@ModelAttribute注释的方法放入模型中的。

In the view, the user should be able to select a Region and Category from a select box. THe options are put in the Model using a @ModelAttribute annotated method on the class level.

问题在于框绑定到lookup属性字段。

THe problem is with the binding of the box to the lookup property fields.

例如,类别字段是ActivityCategory类型,它是一个包含id和description属性的实体类。

For example the Category field is of the ActivityCategory type, which is an entity class containing an id and a description property.

在视图中,选择框填充有可能的选项列表(所有类别均包含ActivityCategory实例),Thymeleaf负责通过将值属性值与列表进行匹配来选择当前值:

In the view, the select box is filled with the list of possible options (allCategories which contains ActivityCategory instances), Thymeleaf takes care of selecting the current value by matching the "value" attribute value with the list:

<label>Categorie</label>
<select th:field="*{category}">
  <option th:each="cat : ${allCategories}"
          th:value="${cat}"
          th:text="${cat.description}">
  </option>
</select>

生成的HTML如下:

<select id="category" name="category">
      <option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@20">Actief en sportief</option>
      <option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@21">Uitgaan en nachtleven</option>
      <option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@22" selected="selected">Kunst en cultuur</option>
      <option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@23">Eten en drinken</option>
      <option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@24" selected="selected">Ontspanning en gezelligheid</option>
</select>

如我们所见,value属性包含对象本身的字符串表示形式,这显然是不希望的,为了显示id值,我们可以使用$ {cat.id}而不是$ {cat},但是当前值的选择(设置'selected = selected'属性)不再起作用。在此之前,我实现了一个Converter,该Converter将ActivityCategory对象转换为int(id值)。在Thymeleaf中,通过使用双赞{{}}来调用转换器:

As we see, the value attributes contain a string representation of the object itself which is clearly not desired, to show the id values we could use ${cat.id} instead of ${cat} but then the selection of the current value (setting the 'selected="selected"' attribute) does not work anymore. THerefore I implemented a Converter which converts an ActivityCategory object to an int (the id value). In Thymeleaf, the converter is called by using the double accolades {{}}:

th:value="${{cat}}"

已创建转换器并将其添加到Spring:

THe converter is created and added to Spring:

public class LookupConverter implements Converter<LookupEntity, String> {
  public String convert(LookupEntity source) {
     return String.valueOf(source.getId());
  }
}

//在MvcConfig类中

//In MvcConfig class

@Override
public void addFormatters(FormatterRegistry registry) {
  registry.addConverter(new LookupConverter());
}

现在,HTML显示选项的id值,这更加合乎逻辑:

Now the HTML shows the id values for the options, which is much more logical:

<select id="category" name="category">
      <option value="1">Actief en sportief</option>
      <option value="2">Uitgaan en nachtleven</option>
      <option value="3" selected="selected">Kunst en cultuur</option>
      <option value="4">Eten en drinken</option>
      <option value="5">Ontspanning en gezelligheid</option>
</select>

但是提交后仍然错误,id值不能绑定到需要ActivityCategory的Activity对象相反,如果是整数值,则会生成typeMismatch验证错误。

But it still wrong after submitting, the id value cannot be bound to the Activity object which expects a ActivityCategory instead if an integer value, so a typeMismatch validation error is generated.

我的处理程序方法如下:

My handler method looks like:

@RequestMapping(value = "/{id}/submit", method = RequestMethod.POST)
public String submitForm(@ModelAttribute("activity") Activity activity, BindingResult result, ModelMap model) {

    if (result.hasErrors()) {
        return "activityform";
    } else {

        if (activity.getId() == null) {
            this.service.saveActivity(activity);
        } else {
            this.service.mergeWithExistingAndUpdate(activity);
        }

        return "redirect:/activity/" + activity.getId() + "/detail";
    }
}

我看过很多帖子,但仍然找不到这个恕我直言的琐碎问题的解决方案。包含id的String值如何被处理程序方法接受并正确转换?还是我们不能为此使用id值?
寻找一些提示...

I have looked at many posts but still found have no solution for this IMHO pretty trivial issue. How can the String value containing the id be accepted by the handler method and properly converted? Or can we not use the id value for this purpose? Looking for some hints...

推荐答案

在另一个论坛的帮助下,我找到了最优雅的解决方案!代替Converter,我们使用Formatter,它可以将特定的Object类型转换为String,反之亦然。格式化程序已注册到Spring,并从Thymeleaf自动调用,并将id字段转换为仅设置了id值的ActivityCategory实例。因此,我们无需从数据库中查找实际实例,因为我们不需要此处的描述,因为Hober吃了ID足以创建查询。

With help from another forum I have found the most elegant solution! Instead of a Converter, we use a Formatter which can convert from specfiec Object type to a String and vice versa. The formatter is registered to Spring and automatically called from Thymeleaf and converts the id field to an ActivityCategory instance with only the id value set. So we do not lookup the actual instance from the database because we do not need the description here, for Hober ate the id is enough to create the query.

我的格式化程序看起来例如:

My formatter looks like:

public class ActivityCategoryFormatter implements Formatter<ActivityCategory> {

@Override
public String print(ActivityCategory ac, Locale locale) {
    // TODO Auto-generated method stub
    return Integer.toString(ac.getId());
}

@Override
public ActivityCategory parse(final String text, Locale locale) throws ParseException {
    // TODO Auto-generated method stub
    int id = Integer.parseInt(text);
    ActivityCategory ac = new ActivityCategory(id);

    return ac;
}
} 

并注册到Spring(与ActivityRegionFormatter一起用于另一个查询字段):

and is registered to Spring (together with the ActivityRegionFormatter for the other lookup field) by:

@Override
public void addFormatters(FormatterRegistry registry) {
//registry.addConverter(new LookupConverter());
registry.addFormatter(new ActivityCategoryFormatter());
registry.addFormatter(new ActivityRegionFormatter());
}

现在它可以按预期工作了!

And now it works as expected!

唯一剩下的问题是我们有一些代码重复,因为两个Formatter类几乎相同,它们仅在传入的泛型类中有所不同。 $ b我试图通过使用由两个查找实体类(ActivityCategory和RegionCategory)实现的公共接口LookupEntity来解决此问题,并使用该公共接口定义格式化程序,但不幸的是,此方法不起作用...

The only remaining issue is that we have some code duplication because the two Formatter classes are almost the same, they only differ in the generic class that is passed in. I tried to solve this by using a common interface LookupEntity which is implemented by the two lookup entity classes (ActivityCategory and RegionCategory) and use this common interface to define the formatter but unfortunately that did not work...

这篇关于Spring MVC复杂对象数据绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆