SpringMVC——使用REST风格实现RESTRUL_CRUD
一、RESTRUL_CRUD_需求
1.1 效果页面介绍
-
显示所有员工信息
URI:emps
请求方式:GET
显示效果:
-
添加员工信息
- 显示添加页面:
URI:emp
请求方式:GET
显示效果:
- 添加员工信息:
URI:emp
显示效果:完成添加,重定向到 list 页面。
- 显示添加页面:
-
删除操作
URL:emp/{id}
请求方式:DELETE
删除后效果:对应记录从数据表中删除 -
修改操作:lastName 不可修改!
- 显示修改页面
URI:emp/{id}
请求方式:GET
显示效果:回显表单。 - 修改员工信息
URI:emp
请求方式:PUT
显示效果:完成修改,重定向到 list 页面。
- 显示修改页面
2.2 CRUD分析
-
项目结构:
-
相关的类:(省略了Service层,为了教学方便)
实体类:Employee、Department
Handler:EmployeeHandler
Dao:EmployeeDao、DepartmentDaoEmployeeDao:
/** * 操作员工的Dao */ @Repository public class EmployeeDao { private static Map<Integer, Employee> employees = null; @Autowired private DepartmentDao departmentDao; static{ employees = new HashMap<Integer, Employee>(); employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA"))); employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB"))); employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC"))); employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD"))); employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE"))); } private static Integer initId = 1006; /** * 员工保存/更新二合一方法 * @param employee */ public void save(Employee employee){ if(employee.getId() == null){ employee.setId(initId++); } employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId())); employees.put(employee.getId(), employee); } /** * 查询所有员工 * @return */ public Collection<Employee> getAll(){ return employees.values(); } /** * 按照id查询某个员工 * @param id * @return */ public Employee get(Integer id){ return employees.get(id); } /** * 删除员工 * @param id */ public void delete(Integer id){ employees.remove(id); } }
DepartmentDao:
/** * 操作部门的dao */ @Repository public class DepartmentDao { private static Map<Integer, Department> departments = null; static{ departments = new HashMap<Integer, Department>(); departments.put(101, new Department(101, "D-AA")); departments.put(102, new Department(102, "D-BB")); departments.put(103, new Department(103, "D-CC")); departments.put(104, new Department(104, "D-DD")); departments.put(105, new Department(105, "D-EE")); } /** * 返回所有的部门 * @return */ public Collection<Department> getDepartments(){ return departments.values(); } /** * 按照部门id查询部门 * @param id * @return */ public Department getDepartment(Integer id){ return departments.get(id); } }
web.xml配置(前端控制器、字符编码过滤器和HiddenHttpMethodFilter过滤器)
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>06_SpringMVC_crud</display-name> <!-- 配置前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all requests to the DispatcherServlet for handling --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置字符编码过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 支持Rest风格转换的filter --> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
-
URUD地址分析:
增删改查的URL地址; /资源名/资源标识 /emp/1 GET:查询id为1的员工 /emp/1 PUT:更新id为1的员工 /emp/1 DELETE:删除id为1的员工 /emp POST:新增员工; /emps GET:查询所有员工
二、RESTRUL_CRUD_显示所有员工信息
- 显示所有员工信息流程:
访问index.jsp----直接发送/emps------控制器查询所有员工------放在请求域中-----转发到list页面展示
index.jsp页面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- 访问项目就要展示员工列表页面 -->
<jsp:forward page="/emps"></jsp:forward>
控制器方法:
/**
* 查询所有员工
* @return
*/
@RequestMapping("/emps")
public String getEmps(Model model){
Collection<Employee> all = employeeDao.getAll();
model.addAttribute("emps", all);
return "list";
}
list.jsp页面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<%
pageContext.setAttribute("ctp", request.getContextPath());
%>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>员工列表页面</title>
<script type="text/javascript" src="${ctp }/script/jquery-1.7.2.js"></script>
</head>
<body>
<h1>员工列表</h1>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
<th>Gender</th>
<th>Department</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<c:forEach items="${requestScope.emps }" var="emp">
<tr>
<td>${emp.id }</td>
<td>${emp.lastName }</td>
<td>${emp.email }</td>
<td>${emp.gender==1?'Male':'Female' }</td>
<td>${emp.department.departmentName }</td>
<td><a href="${ctp }/emp/${emp.id}">Edit</a></td>
<td><!-- 为删除按钮添加点击事件 -->
<a href="${ctp }/emp/${emp.id}" class="deleteBtn">DELETE</a>
</td>
</tr>
</c:forEach>
</table>
<form id="deleteForm" action="${ctp }/emp/${emp.id}" method="POST">
<input type="hidden" name="_method" value="DELETE"/>
</form>
<script type="text/javascript">
$(function(){
$(".deleteBtn").click(function(){
//1、改变表单的action指向
$("#deleteForm").attr("action",this.href);
//2、提交表单
$("#deleteForm").submit();
return false;
});
});
</script>
<a href="${ctp }/toAddpage" >添加员工</a>
</body>
</html>
三、RESTRUL_CRUD_添加操作(使用Spring的表单标签)(重点)
3.1 Spring的表单标签介绍
-
通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显
-
form 标签:
- 一般情况下,通过 GET 请求获取表单页面,而通过 POST 请求提交表单页面,因此获取表单页面和提交表单页面的 URL 是相同的。
- 只要满足该最佳条件的契约,<form:form> 标签就无需通过 action 属性指定表单提交的 URL
- 可以通过 modelAttribute 属性指定绑定的模型属性,若没有指定该属性,则 默认从 request 域对象中读取 command 的表单 bean ,如果该属性值也不存在,则会发生错误。
-
SpringMVC 提供了多个表单组件标签,如 <form:input/>、<form:select/> 等,用以绑定表单字段的属性值,它们的共有属性如下:
- path:表单字段,对应 html 元素的 name 属性,支持级联属性
- htmlEscape:是否对表单值的 HTML 特殊字符进行转换,默认值为 true
- cssClass:表单组件对应的 CSS 样式类名
- cssErrorClass:表单组件的数据存在错误时,采取的 CSS 样式
-
其他各个标签的使用:
- form:input、form:password、form:hidden、form:textarea:对应 HTML 表单的 text、password、hidden、textarea 标签
- form:radiobutton:单选框组件标签,当表单 bean 对应的属性值和 value 值相等时,单选框被选中
- form:radiobuttons:单选框组标签,用于构造多个单选框
- items:可以是一个 List、String[] 或 Map
- itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
- itemLabel:指定 radio 的 label 值
- delimiter:多个单选框可以通过 delimiter 指定分隔符
- form:checkbox:复选框组件。用于构造单个复选框
- form:checkboxs:用于构造多个复选框。使用方式同 form:radiobuttons 标签
- form:select:用于构造下拉框组件。使用方式同 form:radiobuttons 标签
- form:option:下拉框选项组件标签。使用方式同 form:radiobuttons 标签
- form:errors:显示表单组件或数据校验所对应的错误
- <form:errors path= “*” /> :显示表单所有的错误
- <form:errors path= “user*” /> :显示所有以 user 为前缀的属性对应的错误
- <form:errors path= “username” /> :显示特定表单对象属性的错误
3.2 添加操作
- 员工添加流程:
在list页面点击“”员工添加“”----(查询出所有的部门信息要展示在页面)----来到添加页面(add.jsp)--------输入员工数据--------点击保存(/emp )------处理器收到员工保存请求(保存员工)--------保存完成以后还是来到列表页面;
-
在list.jsp上增加连接
<a href="toAddPage">Add Employee</a>
-
处理器方法:
/** * 去员工添加页面,去页面之前需要查出所有部门信息,进行展示 * @return */ @RequestMapping("/toAddpage") public String toAddPage(Model model){ //查询所有部门信息 Collection<Department> departments = departmentDao.getDepartments(); model.addAttribute("deps", departments); //向请求域中传入一个employee对象,SpringMVC的表单标签会从请求域中读取该对象。(默认读取叫command对象) model.addAttribute("employee", new Employee()); // model.addAttribute("command", new Employee(1009,"zhangsan","zhangsan@qq.com",1,null)); return "add"; }
-
添加add.jsp页面(使用Spring的表单标签)
要使用Spring MVC提供的表单标签,首先需要在视图页面添加:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
add.jsp页面:
<%@ 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="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> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>员工添加</h1> <!-- 表单标签; 通过 SpringMVC的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定, 以实现表单数据更便捷编辑和表单值的回显 1)、SpringMVC认为,表单数据中的每一项最终都是要回显的; path指定的是一个属性;这个属性是从隐含模型(请求域中取出的某个对象中的属性); path指定的每一个属性,请求域中必须有一个对象,拥有这个属性; 这个对象就是请求域中的command; --> <!-- 2.可以通过modelAttribute指定绑定的模型属性, 若没有指定该属性,则默认从request域中查找command的表单的bean 如果该属性也不存在,那么,则会发生错误。 --> <% pageContext.setAttribute("ctp", request.getContextPath());//使用绝对路径 %> <form:form action="${ctp }/emp" method="post" modelAttribute="employee"> <!-- path就是原来html-input的name项:需要写 path: 1)、当做原生的name项 2)、自动回显隐含模型中某个对象对应的这个属性的值 --> lastName:<form:input path="lastName"/><br/> email:<form:input path="email"/><br/> gender: <form:radiobutton path="gender" value="1"/>Male <form:radiobutton path="gender" value="0"/>FeMale<br/> department: <!-- items="":指定要遍历的集合 ;自动遍历;遍历出的每一个元素是一个department对象 itemLabel="属性名":指定遍历出的这个对象的哪个属性是作为option标签体的值 itemValue="属性名":指定刚才遍历出来的这个对象的哪个属性是作为要提交 的value值 --> <form:select path="department.id" items="${requestScope.deps }" itemLabel="departmentName" itemValue="id"></form:select><br/> <input type="submit" value="Submit"/> </form:form> <%-- 使用原始的表单标签 <form action="emp" method="post"> LastName:<input type="text" name="lastName"/><br/> Email:<input type="text" name="email"/><br/> Gender:<input type="radio" name="gender" value="1"/>Male<input type="radio" name="gender" value="0"/>Female<br/> Department: <select name="department.id"> <c:forEach items="#{requestScope.deps }" var="dep"> <option value="${dep.id }">${dep.departmentName}</option> </c:forEach> </select><br/> <input type="submit" value="Submit"/> </form> --%> </body> </html>
注意:
用了表单标签的页面可能会报这个错误,请求域中没有一个command类型的对象,所以来到页面之前一定要给请求域中放这个对象。
四、RESTRUL_CRUD_修改操作
- 员工修改修改流程:
4.1 根据id查询员工对象,表单回显
-
修改连接:
<td> <a href="${ctp}/emp/${emp.id}" methods="GET">Edit</a> </td>
-
控制器方法,根据id查询员工信息,再转发到修改页面
/** * 根据id查询员工信息,再转发到修改页面 * @param id * @param model * @return */ @RequestMapping(value="/emp/{id}",method=RequestMethod.GET) public String getEmp(@PathVariable("id")Integer id,Model model){ //1、查询员工信息 Employee employee = employeeDao.get(id); //2、放到请求域中 model.addAttribute("employee", employee); //3、继续查出部门信息放在隐含模型中 model.addAttribute("deps", departmentDao.getDepartments()); return "edit"; }
-
edit.jsp,修改页面上显示待修改的信息
<h1>员工修改页面</h1> <!-- modelAttribute:这个表单的所有内容显示绑定的是请求域中 employee的值--> <form:form action="${ctp }/emp/${employee.id }" method="post" modelAttribute="employee"> <input type="hidden" name="_method" value="PUT"/> <input type="hidden" name="id" value="${employee.id }"/> lastName:${employee.lastName }<br/> email:<form:input path="email"/><br/> gender: <form:radiobutton path="gender" value="1"/>Male <form:radiobutton path="gender" value="0"/>Female<br/> department: <form:select path="department.id" items="${requestScope.deps }" itemLabel="departmentName" itemValue="id"></form:select><br/> <input type="submit" value="Submit"/> </form:form>
4.2 提交表单,修改数据
控制器方法:ModelAttribute提前从数据库中查询员工信息。再重定向到列表页面
/**
* ModelAttribute方法提前查询员工信息
* @param id
* @param model
*/
@ModelAttribute
public void beforeUpdateEmp(@RequestParam(value="id",required=false)Integer id,Model model){
//从隐藏域中获取id
if(id != null){
Employee employee = employeeDao.get(id);
System.out.println("修改员工之前的信息"+employee);
model.addAttribute("employee", employee);
}
}
/**
* 修改员工信息
* @param employee
* @return
*/
@RequestMapping(value="/emp/{id}",method=RequestMethod.PUT)
public String updateEmp(@PathVariable("id")Integer id,@ModelAttribute("employee")Employee employee){
System.out.println("修改的员工信息为"+employee);
employeeDao.save(employee);
return "redirect:/emps";
}
五、RESTRUL_CRUD_删除操作
- 员工删除流程:
-
由于超链接无法发送DELETE请求,所以需要给超链接添加单击事件,通过jQuery提交form表单发送请求。
<td> <!-- 为删除按钮添加点击事件 --> <a href="${ctp }/emp/${emp.id}" class="deleteBtn">DELETE</a> </td> <form id="deleteForm" action="${ctp }/emp/${emp.id}" method="POST"> <input type="hidden" name="_method" value="DELETE"/> </form> <script type="text/javascript"> $(function(){ <!-- 为删除按钮添加点击事件 --> $(".deleteBtn").click(function(){ //1、改变表单的action指向 $("#deleteForm").attr("action",this.href); //2、提交表单 $("#deleteForm").submit(); return false; }); }); </script>
-
控制器方法
/** * 接收DELETE请求,删除员工信息 * @param id * @return */ @RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE) public String deleteEmp(@PathVariable("id")Integer id){ employeeDao.delete(id); //重定向到查询员工的页面 return "redirect:/emps"; }
六、关于静态资源加载(注意)
默认前端控制器是拦截所有资源(除过jsp),js文件就404了
<!-- 默认前端控制器是拦截所有资源(除过jsp),js文件就404了;要js文件的请求是交给tomcat处理的
http://localhost:8080/7.SpringMVC_crud/scripts/jquery-1.9.1.min.js -->
<!-- 告诉SpringMVC,自己映射的请求就自己处理,不能处理的请求直接交给tomcat -->
<!-- 静态资源能访问,动态映射的请求就不能 -->
<mvc:default-servlet-handler/>
<!-- springmvc可以保证动态请求和静态请求都能访问 -->
<mvc:annotation-driven></mvc:annotation-driven>