Spring MVC复杂对象数据绑定

时间:2015-09-01 16:47:02

标签: spring spring-mvc data-binding

我仍在努力使用Spring MVC,这应该是一个相当简单的问题,但在Spring MVC文档中似乎有些细节记录。

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

我的应用程序以Activity类为中心,该类模拟由成员组织的(室内或室外)活动以及其他成员可以订阅的活动。一个Activity有一个Category字段和一个Region字段,它们是由Hibernate建模的下拉字段,是包含id和description字段的DB查找表的多对一实体。

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

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注释方法将选项放入模型中。

问题在于框与查找属性字段的绑定。

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

在视图中,选择框中填充了可能选项列表(包含ActivityCategory实例的allCategories),Thymeleaf负责通过匹配&#34;值&#34;来选择当前值。属性值与列表:

<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属性包含对象本身的字符串表示,显然不需要,以显示我们可以使用$ {cat.id}代替$ {cat}的id值,但随后选择当前值(设置&#39; selected =&#34;已选择&#34;&#39;属性)不再有效。因此,我实现了一个将ActivityCategory对象转换为int(id值)的Converter。在Thymeleaf中,使用双重荣誉{{}}来调用转换器:

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

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

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

//在MvcConfig类中

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

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

<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验证错误。

我的处理程序方法如下:

@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值吗? 寻找一些提示......

2 个答案:

答案 0 :(得分:0)

我认为您无法使用实体模型将表单中的数据提交给MVC控制器。尝试创建一个与表单数据匹配的单独表单对象,并编写一个服务方法将其转换为可以保留在数据库中的实体。

答案 1 :(得分:0)

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

我的格式化工具如下:

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一起):

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

现在它按预期工作了!

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