`

Spring绑定动态列表成员

阅读更多
最近遇到一个需求,使用Spring MVC 3,需要绑定动态列表成员。Google了好多文章,没有找到完美的解决方案,绑定到不成问题,主要是动态添加、删除导致绑定的列表下标,在提交之前需要进行normalize,使用js进行normalize可行,但是费劲而且如果有对表单新的修改导致已有的js出现错误,本文提供了一个简单可行的易于维护的方法:

在添加的时候,我们根据当前条目的数量,计算新添加条目的下标。
在删除的时候,使用js将删除的条目隐藏,并将删除列表的下标记录到隐藏字段中。
这样在后台,我们只需要将删除的条目,按照记录在隐藏字段的下标删除,然后再保存即可。

关于绑定动态列表,在Spring 2.5中,不能使用普通的List,必须使用AutoPopulatingList进行绑定,否则会导致OutOfBoundException,Spring 3提供了Auto Grown的功能,但还仅限于List和Array,map和set还不能Auto Grown。
下面以一个简单的订单修改的例子说明如何进行动态列表绑定:
一个订单,有很多订单条目,用户可以修改订单,动态删除或者添加订单条目。
先看看model:
订单类,一个订单有一个订单列表:
package com.qunar.advertisement.advertiser.model;

import java.util.List;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
public class Order {
	private Long id;
	private String name;
	private List<OrderItem> orderItems;

	public void setId(Long id) {
		this.id = id;
	}

	public Long getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setOrderItems(List<OrderItem> orderItems) {
		this.orderItems = orderItems;
	}

	public List<OrderItem> getOrderItems() {
		return orderItems;
	}
}

订单条目类:
package com.qunar.advertisement.advertiser.model;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
public class OrderItem {
	private Long id;
	private String product;
	private Integer price;

	public void setId(Long id) {
		this.id = id;
	}
	
	public Long getId() {
		return id;
	}
	
	public Integer getPrice() {
		return price;
	}
	
	public void setPrice(Integer price) {
		this.price = price;
	}

	public void setProduct(String product) {
		this.product = product;
	}

	public String getProduct() {
		return product;
	}
}

我们的控制器使用@ModelAttribute注解和表单绑定:
package com.qunar.advertisement.advertiser.controller;

import java.util.Collections;
import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.qunar.advertisement.advertiser.model.Order;
import com.qunar.advertisement.advertiser.service.OrderManger;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
@Controller
@RequestMapping("/orders")
public class OrderController {
	private OrderManger orderManager;
	
	@Autowired
	public void setOrderManger(OrderManger orderManager){
		this.orderManager = orderManager;
	}
	
	
	@RequestMapping("/edit/{id}")
	public ModelAndView edit(@PathVariable("id") Long id){
		return new ModelAndView("/orders/edit_order",Collections.singletonMap("order",  orderManager.getOrderById(id)));
	}
	
	@RequestMapping("/update")
	public ModelAndView update(@ModelAttribute("order") @Valid Order order,@RequestParam(value="deletedIndexes",required=false) List<Integer> deletedIndexes){
		orderManager.update(order,deletedIndexes);
		return new ModelAndView("/orders/home");
	}
}


edit_order.jsp的表单和js添加、删除:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jstl/fn" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<jsp:include page="../base.jsp"></jsp:include>
<head>
	<script src="js/jquery-1.3.1.js" type="text/javascript"></script>
	<title>Order Home</title>
	
	<script type="text/javascript">
		function deleteItem(itemIndex){
			$('#item_' + itemIndex).hide();
			$('#deletedIndexes').append("<input type='hidden' name='deletedIndexes' value='" + itemIndex + "'");
		}
		
		function addItem(){
			var itemCnt = parseInt($('#itemCnt').val());

			var newItem = 
			'<tr id="item_' + itemCnt + '">' + 
				'<th>订单条目' + (itemCnt + 1)+ '</th>' +
				'<td><input type="hidden" name="orderItems[' + itemCnt + '].id"/>' +
					 '<input type="text" name="orderItems[' + itemCnt + '].product"/>' + 
			    '</td>' + 
				'<td><input type="text" name="orderItems[' + itemCnt + '].price"/></td>' +
				'<td><a href="javascript:void(0)" onclick="deleteItem('+ itemCnt + ')">删除条目</a></td>' + 
			'</tr>';
			
			$('#item_' + (itemCnt-1)).after(newItem);
			$('#itemCnt').val(itemCnt + 1);
		}
	</script>
</head>
<body>
	<div id="form">
		<form:form commandName="order" id="orderForm" action="orders/update" method="post">
			<form:hidden path="id"/>
			<div id="deletedIndexes" style="display:none;">
				
			</div>
			<input type="hidden" value="${fn:length(order.orderItems)}" id="itemCnt"/>
			<table>
				<tr>
					<th>订单名</th>
					<td colspan="3"><form:input path="name"/></td>
				</tr>
				<c:forEach varStatus="vs" items="${order.orderItems}">
					<tr id="item_${vs.index}">
						<th>订单条目${vs.index + 1}</th>
						<td><form:hidden path="orderItems[${vs.index}].id"/>
							<form:input path="orderItems[${vs.index}].product"/>
						</td>
						<td><form:input path="orderItems[${vs.index}].price"/></td>
						<td><a href="javascript:void(0)" onclick="deleteItem('${vs.index}')">删除条目</a></td>
					</tr>
				</c:forEach>
				<tr>
					<td colspan="4" style="text-align:center">
						<input type="submit" value="保存"/>
						<input type="button" value="添加订单条目" onclick="addItem()"/>
					</td>
				</tr>
			</table>
		</form:form>
	</div>
</body>
</html>

在Serice类中我们只是简单的准备数据和打印出结果查看效果:
package com.qunar.advertisement.advertiser.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.qunar.advertisement.advertiser.model.Order;
import com.qunar.advertisement.advertiser.model.OrderItem;
/**
 * Author: fuliang
 * http://fuliang.iteye.com
 */
@Service
public class OrderManger {
	//Simple data for test
	public Order getOrderById(Long id){
		Order order = new Order();
		order.setId(id);
		order.setName("手机订单");
		List<OrderItem> orderItems = new ArrayList<OrderItem>();
		order.setOrderItems(orderItems);
		
		OrderItem orderItem1 = new OrderItem();
		orderItem1.setId(1L);
		orderItem1.setProduct("iPhone 4");
		orderItem1.setPrice(5000);
		orderItems.add(orderItem1);
		
		OrderItem orderItem2 = new OrderItem();
		orderItem2.setId(2L);
		orderItem2.setProduct("Nokia N97");
		orderItem2.setPrice(4000);
		orderItems.add(orderItem2);
		
		return order;
	}
	/**
	 * 
	 * @param order should be updated
	 * @param deletedItemIndexes orderItem indexes should be deleted
	 * 简单的在控制台打印出结果,如果存入到数据库,可以根据orderItem id
         * 是否为null,进行更新或者保存。
         */
	public void update(Order order, List<Integer> deletedItemIndexes) {
		if(deletedItemIndexes != null){
			for(int i = deletedItemIndexes.size() - 1; i >= 0; i--){
				order.getOrderItems().remove(deletedItemIndexes.get(i).intValue());
			}
		}
		StringBuilder sb = new StringBuilder();
		sb.append("Order Id: ").append(order.getId()).append("\tName: ").append(order.getName()).append("\n");
		List<OrderItem> items = order.getOrderItems();
		for (OrderItem orderItem : items) {
			sb.append("Item Id: ").append(orderItem.getId()).append("\tName: ").append(orderItem.getProduct()).append("\tprice: ").append(orderItem.getPrice()).append("\n");
		}
		System.out.println(sb.toString());
	}
}


BTW:Java的自动拆装箱要注意,特别是List#remove有两个重载的方法,
remove(Object) remove(int),这个必须手工拆箱。
分享到:
评论
3 楼 fuliang 2011-02-27  
junnyfan 写道
我想同问,如果成员属性是Set如何绑定呢?

好像不可以,绑定的需要能够按照下标访问的,而Set本身不能确保有序,所以不能下标访问。最好在VO中使用List绑定,然后和PO的Set转换一下。
2 楼 junnyfan 2011-02-27  
我想同问,如果成员属性是Set如何绑定呢?
1 楼 蓝之月 2011-01-20  
如果对象成员为SET,如何绑定呢?

相关推荐

    成员管理程序Spring版

    在成员管理程序中,通过注解可以快速实现组件的配置和绑定。 7. **单元测试**:Spring Test模块提供了对Spring应用的测试支持,我们可以编写单元测试和集成测试,确保成员管理程序的各个部分功能正常。 8. **...

    最新spring官网下载jar包 spring-4.2.4(免费)

    最后,Spring Boot是Spring的新成员,旨在简化Spring应用程序的初始搭建和配置过程。虽然标题没有明确提及Spring Boot,但4.2.4版本的Spring框架与Spring Boot的兼容性很好,可以无缝集成。 综上所述,"最新spring...

    spring配置文件:整理总结Spring中XML配

    - Spring提供了一种自动绑定机制,该机制允许框架根据类的属性名称或类型自动绑定依赖关系。尽管这看起来可以简化配置,但在实际项目中并不推荐使用。自动绑定牺牲了配置的清晰度和可维护性,特别是在大型项目中。 ...

    spring annotation注解

    第二种实现中,Spring 会将 userDao 这个成员变量的 setter 方法与 userDao bean 进行绑定。 2. @Autowired 注解的使用 @Autowired 注解可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作。例如: ...

    Spring5.1中文参考指南.pdf

    - **反馈与贡献**:鼓励社区成员提出问题、建议并参与框架的开发与维护。 #### 二、核心特性详解 - **IoC容器** - **IoC容器介绍**:是Spring框架的核心,用于管理对象的生命周期和配置。 - **IoC容器概述**:...

    spring.net中文手册在线版

    4.3.5.引用其他对象或类型的成员 4.3.5.1.使用对象或类的属性值进行注入 4.3.5.2.使用字段值进行注入 4.3.5.3.使用方法的返回值进行注入 4.3.6.IFactoryObject接口的其它实现 4.3.6.1.Log4Net 4.3.7.使用depends-on ...

    日志组件(spring依赖)

    在Spring应用中使用日志时,我们通常会创建一个名为`Logger`的静态成员变量,它是`org.slf4j.Logger`或`org.apache.log4j.Logger`的实例,然后在需要记录日志的地方调用其提供的方法,如`info()`, `debug()`, `error...

    基于Spring Boot的BeHealthy健康管理平台.zip

    基于Spring Boot的BeHealthy健康管理平台 BeHealthy 是一个综合性的健康管理平台,旨在为用户提供全面的健康记录、家庭成员管理、健康新闻浏览以及医疗听诊服务。该项目采用微服务架构,结合Spring Boot、Spring ...

    使用 SSM(Spring MVC + Spring + MyBatis)框架实现申报项目信息管理系统实验报告

    - 掌握Spring MVC中的数据绑定、校验机制。 4. **掌握Spring与MyBatis的框架集成**: - 学习如何将MyBatis与Spring结合使用,包括配置数据源、SqlSessionFactory等。 - 了解Spring如何通过AOP实现事务管理。 - ...

    自已实现spring ioc功能代码 jdk1.6的一些新特性

    在本主题中,我们将深入探讨如何使用JDOM库在Java中实现Spring的IoC(Inversion of Control,...然而,实际的Spring框架提供了许多高级功能,如AOP(面向切面编程)、事务管理、数据绑定等,这些都需要更复杂的实现。

    Spring教程1

    此外,Spring还提供了多种技术,如事件处理、资源管理、国际化支持、数据绑定、类型转换、Spring表达式语言(SpEL)以及面向切面编程(AOP),这些都构成了Spring的核心技术。 Spring的测试支持涵盖模拟对象、...

    Spring 2.5 基于注解驱动的 Spring MVC.docx

    1. **@Autowired**:这是 Spring 自动装配依赖注入的注解,用于将依赖的服务(如 BbtForumService)自动注入到 Controller 类的成员变量中。这样就避免了在 XML 配置文件中手动配置 Bean 的依赖关系。 2. **@...

    数据绑定之JAXB

    2. **注解驱动的绑定**:学习如何使用`@XmlRootElement`,`@XmlElement`,`@XmlAttribute`等注解来标记Java类和其成员,以便JAXB理解如何进行转换。 3. **绑定配置文件**:在某些复杂场景下,可能需要使用`bindings...

    Spring Annotations 卡片

    当一个类被Spring容器管理时,该注解可以自动将类型匹配的bean注入到标记了此注解的成员变量或方法参数中。例如: ```java @Autowired private TreasureMap treasureMap; ``` - **@Qualifier**:用于解决@...

    SpringMvc+Spring+MyBatis+Maven

    MyBatis通过XML或注解的方式配置SQL和结果映射,与Spring整合后,可以实现SQL的动态执行和事务管理。在"stuManagement"项目中,MyBatis可能被用来执行学生的增删改查操作,与数据库进行交互。 最后,Maven是项目...

    java (jvm + juc)+ spring + springcloud + sql + redis 面试大全

    封装是将数据和操作数据的方法绑定在一起,通过get和set方法对外提供访问接口,以保护数据的安全性。继承则允许子类继承父类的属性和方法,实现代码复用,并且支持单一继承和多重继承。多态则是指同一行为在不同对象...

    java中spring与Quartz 整合定时任务.pdf

    在`SampleTask`类中,我们可以看到它包含了一些成员变量,如`WorkService`、`users`和`teams`,这些都是任务执行时可能需要用到的数据。`setWorkService`和`setWork`方法用于注入依赖的服务和对象,这是Spring的依赖...

    spring-boot笔记

    - **作用**: 自动装配 Bean,可以用于类成员变量、方法及构造函数。 - **默认行为**: 按照类型进行匹配。 - **@Qualifier**: 当存在多个相同类型的 Bean 时,可以使用 @Qualifier 指定具体装配哪一个。 2. **...

Global site tag (gtag.js) - Google Analytics