【问题标题】:How to receive complex objects in spring 3.2 mvc?spring 3.2 mvc如何接收复杂对象?
【发布时间】:2013-11-13 15:29:21
【问题描述】:

spring 3.2 mvc如何接收复杂对象?

在下面的简单示例中,我有两个模型类,具有多对一的关系。添加新的 Employee 对象时,我想使用 html 选择来选择它的部门。

当我发布添加新员工时,我收到以下错误:

无法将 java.lang.String 类型的属性值转换为属性部门所需的 hu.pikk.model.Department 类型;嵌套异常是 java.lang.IllegalStateException:无法将类型 [java.lang.String] 的值转换为属性部门所需的类型 [hu.pikk.model.Department]:找不到匹配的编辑器或转换策略

我应该如何实施编辑器或转换策略?是否有值得关注的最佳做法或陷阱?

我已经阅读了 spring mvc 文档,以及一些文章和 stackoverflow 问题,但老实说,我发现它们有点令人困惑,而且很多时候太短,太随意了。

型号:

@Entity
public class Employee {
    @Id
    @GeneratedValue
    private int employeeId;
    @NotEmpty
    private String name;

    @ManyToOne
    @JoinColumn(name="department_id")
    private Department department;
    //getters,setters
}

@Entity
public class Department {
    @Id
    @GeneratedValue
    private int departmentId;
    @Column
    private String departmentName;

    @OneToMany
    @JoinColumn(name = "department_id")
    private List<Employee> employees;
    //getters,setters
}

在我的控制器类中:

@RequestMapping(value = "/add", method = RequestMethod.GET)
private String addNew(ModelMap model) {
    Employee newEmployee = new Employee();
    model.addAttribute("employee", newEmployee);
    model.addAttribute("departments", departmentDao.getAllDepartments());
    return "employee/add";
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
private String addNewHandle(@Valid Employee employee, BindingResult bindingResult, ModelMap model, RedirectAttributes redirectAttributes) {
    if (bindingResult.hasErrors()) {
        model.addAttribute("departments", departmentDao.getAllDepartments());
        return "employee/add";
    }
    employeeDao.persist(employee);
    redirectAttributes.addFlashAttribute("added_employee", employee.getName());
    redirectAttributes.addFlashAttribute("message", "employee added...");
    return "redirect:list";     
}

在add.jsp中:

<f:form commandName="employee" action="${pageContext.request.contextPath}/employee/add" method="POST">
    <table>
        <tr>
            <td><f:label path="name">Name:</f:label></td>
            <td><f:input path="name" /></td>
            <td><f:errors path="name" class="error" /></td>
        </tr>
        <tr>
            <td><f:label path="department">Department:</f:label></td>
        <td><f:select path="department">
                <f:option value="${null}" label="NO DEPARTMENT" />
                <f:options items="${departments}" itemLabel="departmentName" itemValue="departmentId" />
            </f:select></td>
            <td><f:errors path="department" class="error" /></td>
        </tr>
    </table>
    <input type="submit" value="Add Employee">
</f:form>

【问题讨论】:

  • 你应该做的是显示Department名称的下拉列表,但只发布部门的ID。然后,您可以检索Department 并将其设置为新的Employee
  • @SotiriosDelimanolis 如果我没记错的话,这就是我在 jsp 中一直在做的事情。问题是,当控制器收到 POST 时,它不知道如何将 id 字符串转换为 Department 对象。如果我理解正确,我必须为此实现并注册一个 Converter 。我想知道这方面是否有最佳实践。比如这个东西怎么打包,哪个更好,editor还是converter,是不是应该在Converter里面查询数据库,还是有更好的办法?等等……
  • 它正在尝试将 id 转换为 Department,因为请求参数名称是 department。如果你将它(不要使用form标签库)更改为&lt;input name="departmentId"&gt;并为其添加@RequestParam,则可以从某个DAO层查询Department对象并调用Employee Department 的设置器。我认为在这里执行此操作比在任何Converter 中执行此操作要好。

标签: java spring-mvc data-binding


【解决方案1】:

问题是,当控制器收到 POST 时,它不知道如何将 id 字符串转换为 Department 对象。 我发现基本上有三种方法可以解决这个问题:

  1. 不使用 spring form jstl,而是使用自定义名称的简单 html 选择,并在 Controller 中使用 @RequestParam 读取它,访问数据库并填充它。
  2. 实现 Converter 接口,并将其注册为 bean。
  3. 实现 PropertyEditor 接口。通过扩展 PropertyEditorSupport 类更容易做到这一点。

我选择了第三个选项。 (稍后我将有一些时间,我将使用前两个选项来编辑这个答案。)

2。实现 Converter 接口

@Component 
public class DepartmentConverter implements Converter<String,Department>{
    @Autowired
    private DepartmentDao departmentDao;
    @Override
    public Department convert(String id){
        Department department = null;
        try {
            Integer id = Integer.parseInt(text);
            department = departmentDao.getById(id);
            System.out.println("Department name:" + department.getDepartmentName());
        } catch (NumberFormatException ex) {
            System.out.println("Department will be null");
        }
        return department;
    }
}

在spring beans配置文件中:

<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService"
  class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="package.DepartmentConverter"/>
        </list>
    </property>
</bean>

3。扩展 PropertyEditorSupport 类

public class SimpleDepartmentEditor extends PropertyEditorSupport {

    private DepartmentDao departmentDao;

    public SimpleDepartmentEditor(DepartmentDao departmentDao){
        this.departmentDao = departmentDao;
    }
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        Department department = null;
        try {
            Integer id = Integer.parseInt(text);
            department = departmentDao.getById(id);
            System.out.println("Department name:" + department.getDepartmentName());
        } catch (NumberFormatException ex) {
            System.out.println("Department will be null");
        }
        setValue(department);
    }
}

在控制器内部,我需要添加一个@InitBinder:

    @InitBinder
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
        binder.registerCustomEditor(Department.class, new SimpleDepartmentEditor(departmentDao));
    }

【讨论】:

    【解决方案2】:

    在我看来,您在这里遇到了下一种情况。当 Spring 尝试反序列化在 addNewHandle 中接收到的 Employee 实体时,它找到了 String 类型的属性 department 但在目标实体中它具有 Department 类型,然后,因为您没有此类转换的转换注册表, 它失败。因此,为了解决这个问题,您可以实现自己的转换器 (Converter),它获取 String 并返回 Department 并将其注册到 conversionService 中,或者您可以通过覆盖 Jackson JsonDeserializer 并注释 department 属性来实现自己的 JSON 反序列化策略与@JsonDeserialize(using=&lt;your Jacson custom deserializer&gt;.class)。希望这会有所帮助。

    【讨论】:

    • JSON 在这里没有位置。这是一个表单,&lt;input&gt; 元素将是 url 编码的表单参数。
    • 感谢您指出我的错误。你是对的。但我仍然认为可以通过引入自定义 'Converter' 来解决这个问题。
    • 我不建议在Converter 中这样做。您可能希望检索Department 并在同一事务中创建Employee。如果您在 Converter 中检索到 Department,这将很难做到。
    • 当 Spring 将表单反序列化为目标实体时,它会创建目标实体对象,然后迭代 overtform 属性并尝试在目标实体对象中设置适当的字段。在设置目标实体对象字段时,Spring尝试通过类型转换的方式将源表单属性值转换为目标字段值,可以使用合适的Converter。因此,对于整个 Employee 实体,我们不需要 Converter,但对于部门属性,我们需要 Converter。如我错了请纠正我。提前致谢。
    • 没错。在这种情况下,Employee 是一个新对象,尚未持久化。但我们可以假设Department 存在于持久存储中,这就是 OP 所指的那个。
    猜你喜欢
    • 2014-03-02
    • 2013-06-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多