使用springmvc实现基于页面的增删改查,只要对Controller不同方法返回不同的的View(页面或json/xml)即可。
本节示例对Teacher集合进行CRUD的操作。
1.设计
设计Teacher模块前台需要一个页面进行列表展示和交互,通过ajax异步提交form并返回json结果;后台需要提供查询列表、创建、删除、修改、查询等方法。考虑到分页需求,还需要一个统计总数方法。
地址 |
请求方法 |
说明 |
/teacher |
GET |
模块入口,返回jsp |
/teacher/list |
GET |
查询teacher集合 |
/teacher/count |
GET |
查询teacher数量 |
/teacher/get |
GET |
根据id查询某个teacher |
/teacher/save |
POST |
创建新teacher,content中包含资源内容 |
/teacher/update |
POST |
更新teacher,content中包含资源内容 |
/resource/remove |
GET |
根据id删除teacher |
2.Teacher POJO、DAO、SERVICE
在com.sunbin.test.teacher包下创建POJO、DAO、SERVICE。
Teacher类:
package com.sunbin.test.teacher.pojo;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "Teacher")
public class Teacher {
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [id=" + id + ", age=" + age + ", name=" + name + "]";
}
}
@XmlRootElement(name = "Teacher")注解使得Teacher类可以被转换成xml/json格式输出。
TeacherService接口:
package com.sunbin.test.teacher.service;
import java.util.List;
import com.sunbin.test.teacher.pojo.Teacher;
public interface TeacherService {
@SuppressWarnings("rawtypes")
public List list();
public int count();
public void save(Teacher teacher);
public void remove(Teacher teacher);
public void update(Teacher teacher);
public Teacher get(Teacher teacher);
}
定义了6个服务层抽象方法:查询列表、统计总数、创建、删除、修改、查询。
TeacherDao接口:
package com.sunbin.test.teacher.dao;
import java.util.List;
import com.sunbin.test.teacher.pojo.Teacher;
public interface TeacherDao {
@SuppressWarnings("rawtypes")
public List list();
public int count();
public void save(Teacher teacher);
public void remove(Teacher teacher);
public void update(Teacher teacher);
public Teacher get(Teacher teacher);
}
定义了与service接口对应的6个数据层抽象方法。
3.接口实现
示例中不使用数据库,而是使用一个List来存储Teacher集合。
TeacherDaoImpl数据层接口实现:
package com.sunbin.test.teacher.dao.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.sunbin.test.teacher.dao.TeacherDao;
import com.sunbin.test.teacher.pojo.Teacher;
@Repository("teacherDao")
public class TeacherDaoImpl implements TeacherDao {
private static List<Teacher> teachers = new ArrayList<Teacher>();
@SuppressWarnings("rawtypes")
@Override
public List list() {
System.out.println("TeacherDaoImpl.list:" + teachers);
return teachers;
}
@Override
public int count() {
System.out.println("TeacherDaoImpl.count:" + teachers.size());
return teachers.size();
}
@Override
public void save(Teacher teacher) {
System.out.println("TeacherDaoImpl.save:" + teacher);
int teacherId = 1;
if (teachers.size() > 0) {
teacherId = teachers.get(teachers.size() - 1).getId() + 1;
}
teacher.setId(teacherId);
teachers.add(teacher);
}
@Override
public void remove(Teacher teacher) {
System.out.println("TeacherDaoImpl.remove:" + teacher);
int teacherId = teacher.getId();
for (int i = 0; i < teachers.size(); i++) {
Teacher teacherI = teachers.get(i);
if (teacherI.getId() == teacherId) {
teachers.remove(i);
return;
}
}
}
@Override
public void update(Teacher teacher) {
System.out.println("TeacherDaoImpl.update:" + teacher);
int teacherId = teacher.getId();
for (int i = 0; i < teachers.size(); i++) {
Teacher teacherI = teachers.get(i);
if (teacherI.getId() == teacherId) {
teachers.remove(i);
teachers.add(i, teacher);
return;
}
}
}
@Override
public Teacher get(Teacher teacher) {
System.out.println("TeacherDaoImpl.get:" + teacher);
int teacherId = teacher.getId();
for (int i = 0; i < teachers.size(); i++) {
Teacher teacherI = teachers.get(i);
if (teacherI.getId() == teacherId) {
return teacherI;
}
}
return null;
}
}
该类实现了数据层接口,且@Repository("teacherDao")注解声明一个名为teacherDao的数据层Bean。
TeacherServiceImpl服务层接口实现:
package com.sunbin.test.teacher.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sunbin.test.teacher.dao.TeacherDao;
import com.sunbin.test.teacher.pojo.Teacher;
import com.sunbin.test.teacher.service.TeacherService;
@Service("teacherService")
public class TeacherServiceImpl implements TeacherService {
@Autowired
private TeacherDao teacherDao;
@SuppressWarnings("rawtypes")
@Override
public List list() {
System.out.println("TeacherServiceImpl.list");
return teacherDao.list();
}
@Override
public int count() {
System.out.println("TeacherServiceImpl.count");
return teacherDao.count();
}
@Override
public void save(Teacher teacher) {
System.out.println("TeacherServiceImpl.save:" + teacher);
teacherDao.save(teacher);
}
@Override
public void remove(Teacher teacher) {
System.out.println("TeacherServiceImpl.remove:" + teacher);
teacherDao.remove(teacher);
}
@Override
public void update(Teacher teacher) {
System.out.println("TeacherServiceImpl.update:" + teacher);
teacherDao.update(teacher);
}
@Override
public Teacher get(Teacher teacher) {
System.out.println("TeacherServiceImpl.get:" + teacher);
return teacherDao.get(teacher);
}
}
该类实现了服务层接口,且@Service("teacherService")注解声明一个名为teacherService的服务层Bean,@Autowired注解将注入teacherDao bean。
4.Contoller
Conroller需要实现设计的7个方法:
package com.sunbin.test.teacher.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import com.sunbin.test.teacher.pojo.Teacher;
import com.sunbin.test.teacher.service.TeacherService;
@Controller
@RequestMapping("/teacher")
public class TeacherController {
@Autowired
private TeacherService teacherService;
@RequestMapping("")
public ModelAndView index(HttpServletRequest arg0, HttpServletResponse arg1)
throws Exception {
ModelAndView modelAndView = new ModelAndView("teacher/index");
return modelAndView;
}
@RequestMapping("/list")
public ModelAndView list(HttpServletRequest arg0, HttpServletResponse arg1)
throws Exception {
ModelAndView modelAndView = new ModelAndView(
new MappingJackson2JsonView());
modelAndView.addObject("list", teacherService.list());
return modelAndView;
}
@RequestMapping("/count")
public ModelAndView count(HttpServletRequest arg0, HttpServletResponse arg1)
throws Exception {
ModelAndView modelAndView = new ModelAndView(
new MappingJackson2JsonView());
modelAndView.addObject("count", teacherService.count());
return modelAndView;
}
@RequestMapping(value = "/save", method = { RequestMethod.POST })
public ModelAndView save(Teacher teacher, HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
ModelAndView modelAndView = new ModelAndView(
new MappingJackson2JsonView());
teacherService.save(teacher);
modelAndView.addObject("status", "y");
return modelAndView;
}
@RequestMapping("/remove")
public ModelAndView remove(Teacher teacher, HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
ModelAndView modelAndView = new ModelAndView(
new MappingJackson2JsonView());
teacherService.remove(teacher);
modelAndView.addObject("status", "y");
return modelAndView;
}
@RequestMapping(value = "/update", method = { RequestMethod.POST })
public ModelAndView update(Teacher teacher, HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
ModelAndView modelAndView = new ModelAndView(
new MappingJackson2JsonView());
teacherService.update(teacher);
modelAndView.addObject("status", "y");
return modelAndView;
}
@RequestMapping("/get")
public ModelAndView get(Teacher teacher, HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
ModelAndView modelAndView = new ModelAndView(
new MappingJackson2JsonView());
modelAndView.addObject("teacher", teacherService.get(teacher));
return modelAndView;
}
}
除了首页使用teacher/index.jsp展示,其他接口返回MappingJackson2JsonView。
save和update配置了method = { RequestMethod.POST },必须使用POST方法访问。
Controller中的方法可以使用Teacher作为参数,springmvc自动将页面输入组装成pojo传入后台。
5.首页
在WEB-INF/jsp/teacher/下创建index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!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>teacher</title>
<script type="text/javascript" src="<%=path %>/resources/js/jquery-3.2.0.js"></script>
<script type="text/javascript">
$(document).ready(function(){
list();
var ajaxOption = null;
$("#saveButton").click(function(){
save();
});
});
function list(){
$.ajax({
url:"teacher/list",
dataType:"json",
success:function(data){
$("#listBody").empty();
$.each(data.list, function(i, item){
var teacherTr = "<tr><form name='updateForm_"+item.id+"' action='teacher/update' method='POST'>"
+"<td><input type='hidden' name='id' id='id_"+item.id+"' value='"+item.id+"'/>"+item.id+"</td>"
+"<td><input type='text' name='name' id='name_"+item.id+"' value='"+item.name+"' /></td>"
+"<td><input type='text' name='age' id='age_"+item.id+"' value='"+item.age+"' /></td>"
+"<td><a href='javascript:void(0);' onclick='get("+item.id+");'>get</a> "
+"<a href='javascript:void(0);' onclick='remove("+item.id+");'>remove</a> "
+"<input type='submit' value='update' id='updateButton_"+item.id+"' onclick='update("+item.id+");' /></td></tr>";
//alert(teacherTr);
$("#listBody").append(teacherTr);
});
count();
}
});
}
function count(){
$.ajax({
url:"teacher/count",
dataType:"json",
success:function(data){
$("#count").html(data.count);
}
});
}
function save(){
$("#saveButton").attr("disabled", true);
$.ajax({
url:"teacher/save",
type:"POST",
data:"name="+$("#name").val()+"&"+"age="+$("#age").val(),
dataType:"json",
complete:function(){
$("#saveButton").attr("disabled", false);
},
success:function(data){
alert(data.status);
list();
}
});
}
function get(id){
$.ajax({
url:"teacher/get",
data:"id="+id,
dataType:"json",
success:function(data){
var teacherInfo = "id:"+data.teacher.id+"\nname:"+data.teacher.name+"\nage:"+data.teacher.age;
alert(teacherInfo);
}
});
}
function remove(id){
$.ajax({
url:"teacher/remove",
data:"id="+id,
dataType:"json",
success:function(data){
alert(data.status);
list();
}
});
}
function update(id){
$("#updateButton_"+id).attr("disabled", true);
$.ajax({
url:"teacher/update",
type:"POST",
data:"id="+id+"&name="+$("#name_"+id).val()+"&"+"age="+$("#age_"+id).val(),
dataType:"json",
complete:function(data){
$("#updateButton_"+id).attr("disabled", false);
},
success:function(data){
alert(data.status);
list();
}
});
}
</script>
</head>
<body>
save
<form name="saveForm" action="teacher/save" method="POST">
name:<input type="text" name="name" id="name" />
age:<input type="text" name="age" id="age" />
<input type="button" value="save" id="saveButton" />
</form>
count:<span id="count"></span>
<br/>
list
<table border="1">
<thead>
<tr>
<td>id</td>
<td>name</td>
<td>age</td>
<td>操作</td>
</tr>
</thead>
<tbody id="listBody">
</tbody>
</table>
</body>
</html>
list()方法GET teacher/list,对返回的teacher集合使用表格展示,并调用count()更改统计数量。
count()方法GET teacher/count,显示返回数量。
save()方法将数据POST至teacher/save保存,成功后刷新表格。
get(id)方法GET teacher/get,弹窗显示单条记录。
remove(id)方法GET teacher/remove,删除单挑记录,成功后刷新表格。
update(id)方法将数据POST至teacher/save更新,成功后刷新表格。
6.测试
重新部署至tomcat,访问 http://localhost:8080/testRest/teacher。
可看到页面内容,并进行增删改查操作
7.springmvc PUT/DELETE调用及参数的坑
如果想要支持http的PUT和DELETE方法,实现RESTFUL接口,只需将Controller方法的注解改为PUT/DELETE:
@RequestMapping(value = "/update", method = { RequestMethod.PUT })
可以使用ajax直接调用PUT请求:
$.ajax({
url:"teacher/update",
type:"PUT",
data:"id="+id+"&name="+$("#name_"+id).val()+"&"+"age="+$("#age_"+id).val(),
...
如果通过表单form调用就没这么简单了。因为html的form只支持GET、POST方法,不能直接发送PUT、DELETE请求。springmvc为了解决这个问题,增加了特殊的过滤器。
在web.xml中增加配置:
<filter>
<filter-name>HttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpMethodFilter</filter-name>
<servlet-name>spring</servlet-name>
</filter-mapping>
配置拦截器处理隐藏参数。HiddenHttpMethodFilter主要源码如下:
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
/** Default method parameter: {@code _method} */
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
/**
* Set the parameter name to look for HTTP methods.
* @see #DEFAULT_METHOD_PARAM
*/
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String paramValue = request.getParameter(this.methodParam);
if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
}
else {
filterChain.doFilter(request, response);
}
}
拦截器会处理POST请求中的_method参数,转换为PUT/DELETE请求。
修改页面form,增加_method参数:
<form action="..." method="POST">
<input type="hidden" name="_method" value="PUT">
...
</form>
这种配置虽然能调用到PUT/DELETE,但是参数传递会出现问题,因为springmvc的HiddenHttpMethodFilter不能组装参数成pojo。增加以下拦截器:
<filter>
<filter-name>HttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpMethodFilter</filter-name>
<servlet-name>spring</servlet-name>
</filter-mapping>
查看HttpPutFormContentFilter拦截器源码,可以看到是对PUT请求的contentbody进行处理。
@Override
protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && isFormContentType(request)) {
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
@Override
public InputStream getBody() throws IOException {
return request.getInputStream();
}
};
MultiValueMap<String, String> formParameters = formConverter.read(null, inputMessage);
HttpServletRequest wrapper = new HttpPutFormContentRequestWrapper(request, formParameters);
filterChain.doFilter(wrapper, response);
}
else {
filterChain.doFilter(request, response);
}
}
两种方法都可以发送PUT请求调用/update。
该拦截器只能处理PUT的contentbody。
对于DELETE请求,不能获取通过body提交的内容,只能使用参数或者地址变量。
POST content:
function remove(id){
$.ajax({
url:"teacher/remove",
type:"POST",
data:"id="+id,
修改为DELETE请求参数:
function remove(id){
$.ajax({
url:"teacher/remove?id="+id,
type:"DELETE",
Controller方法:
@RequestMapping(value = "/remove", method = { RequestMethod.DELETE })
public ModelAndView remove(Integer id,
HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
ModelAndView modelAndView = new ModelAndView(
new MappingJackson2JsonView());
Teacher teacher = new Teacher();
teacher.setId(id);
teacherService.remove(teacher);
modelAndView.addObject("status", "y");
return modelAndView;
}
通过ajax发送DELETE请求调用/remove地址成功。
同样可以使用form隐藏参数:
<form action="..." method="POST">
<input type="hidden" name="_method" value="DELETE">
...
</form>