Web开发(CRUD)
一、实验要求
1. 使用Rest风格:URI+请求方式
实验功能 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询员工(来到修改页面) | emp/{id} | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工信息进行回写) | emp/{id} | GET |
修改员工 | emp | PUT |
删除员工 | emp/{id} | DELETE |
2. 实体类和Dao:
/entities/Employee
:员工类
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
private Department department;
private Date birth;
/entities/Department
:部门类
private Integer id;
private String departmentName;
/dao/EmployeeDao
:
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
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;
}
/dao/DepartmentDao
:
@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"));
}
public Collection<Department> getDepartments(){
return departments.values();
}
public Department getDepartment(Integer id){
return departments.get(id);
}
}
二、查询所有员工
1. 创建EmployeeController
类,处理各种请求
2. 在EmployeeController
中定义list
方法,发送emps
请求+GET
请求方式(GetMapping
),查询所有员工,返回/templates/emp/list.html
列表页面
具体代码:
EmployeeController
:
@Controller
public class EmployeeController {
/*
* 将EmployeeDao自动注入,目前Dao没有涉及到数据库操作,直接在Dao中定义了一些Emp
*/
@Autowired
private EmployeeDao employeeDao;
/*
* 查询所有员工
*/
@GetMapping("/emps")
public String list(Model model){
// 查询所有员工
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps", employees);
return "emp/list";
}
}
(1)使用@GetMapping("/emps")
发送emp
+GET
请求,返回"emp/list"
页面
(2)将查询到的员工employees
存储到Model
中,用于在页面调用
注意:thymeleaf默认会进行拼串:找到classpath:/templates/emp/list.html
页面
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
3. 抽取并引入页面的公共片段:顶部和侧边栏
(1)抽取公共片段(为公共片段命名):
- <div th:fragment="片段名">
- <nav id="片段名>
(2)从dashboard
模板中引入公共片段:
- <div th:insert="~{模板名::片段名}">
:将公共片段整个插入到需要引入的元素的div
中
- <div th:replace="模板名::片段名">
:将需要引入的元素(包括div
)替换为公共片段
- th:include
:将被引入的片段的内容包含进这个标签中
- 使用以上三种方式引入,可以不写~{ }
(3)将dashboard
模板中定义的公共片段也抽取出来,单独放在/templates/commons/bar.html
中
(4)对公共片段的侧边栏设置高亮显示
- 点击dashboard
,url指向/main.html
页面
- 点击员工管理
,url发送/emps
请求,指向list.html
页面
具体代码:
/templates/commons/bar.html
:抽取出来的公共片段
<!-- topbar -->
<!-- 将顶部公共页面抽取出来 th:fragment="topbar" -->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<!-- 显示登录的用户名 -->
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
<!-- ...省略... -->
</nav>
<!-- sidebar -->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
...省略...
<li class="nav-item">
<a class="nav-link active" href="#" th:href="@{/main.html}"
th:class="${activeUri=='main.html'?'nav-link active': 'nav-link'}">
<!-- ...省略... -->
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<!-- 点击员工管理,查询所有员工,发送/emps请求 -->
<!-- 将sidebar的员工管理点击后指向emps
th:class:判断Uri,如果是emps,那么就高亮
-->
<a class="nav-link active " href="#" th:href="@{/emps}"
th:class="${activeUri=='emps'?'nav-link active':'nav-link'}">
<!-- ...省略... -->
员工管理
</a>
</li>
(1)使用th:fragment="topbar"
定义抽取出来的头部片段,使用id="sidebar"
定义抽取出来的侧边栏片段
(2)在侧边栏sidebar中,使用th:href="@{/main.html}"
来替换掉原生属性,使得点击侧边栏上的dashboard
可以指向main.html
,使用th:class="${activeUri=='main.html'?'nav-link active': 'nav-link'}"
来进行判断,如果当前uri是main.html
,那么高亮。
(3)在侧边栏sidebar中,使用th:href="@{/emps}"
来替换掉原生属性,使得点击侧边栏上的员工管理
可以发送emps
请求,使用th:class="${activeUri=='emps'?'nav-link active': 'nav-link'}"
来进行判断,如果当前uri是emps
,那么高亮。
/templates/dashboard.html
:引入带参数的公共片段,使之高亮
<body>
<div th:replace="commons/bar::topbar"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div>
<!-- ...省略... -->
</div>
</div>
(1)使用<div th:replace="commons/bar::topbar"></div>
来引入定义的头部公共片段
(2)使用<div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div>
来引入定义的侧边栏公共片段,并且带上了参数(activeUri='main.html')
,这里的( )表示?
,当前在dashboard
页面下,uri=main.html
,因此高亮。
/templates/emp/list.html
:引入带参数的公共片段,使之高亮
<body>
<div th:replace="commons/bar::topbar"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
<!-- ...省略... -->
</div>
</div>
(1)使用<div th:replace="commons/bar::topbar"></div>
来引入定义的头部公共片段
(2)使用<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
来引入定义的侧边栏公共片段,并且带上了参数(activeUri='emps')
,这里的( )表示?
,当前发送emps请求,在list.html
页面下,uri=emps
,因此高亮。
4. 将Controller中查询到的emps在页面遍历显示:
在EmployeeController
中我们使用model.addAttribute("emps", employees);
将查询到的员工数据都保存在了model里,那么在list
页面可以使用${emps.属性名}
来调用
list.html
页面:
(1)列名:
<thead>
<tr>
<th>#</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
(2)显示数据:
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td>[[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}==0?'女':'男'"></td>
<td th:text="${emp.department.departmentName}"></td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>
</tr>
</tbody>
在tr
标签里使用th:each="emp:${emps}"
遍历存储在model里的emps数据,每次遍历到的emp
,使用th:text="${emp.属性名}"
来获得具体数据。
三、添加员工
1. 在list.html
页面配置员工添加
按钮,发送/emp
+GET
请求
2. 在EmployeeController
中定义toAddPage
方法,发送emp
+GET
请求来到添加员工的页面/templates/emp/add.html
具体代码:
list.html
:
<h2><a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">员工添加</a></h2>
(1)使用th:href="@{/emp}"
指向emp的请求
EmployeeController
:
@GetMapping("/emp")
public String toAddPage(Model model){
// TODO 6.6.4 查出所有部门放到model里,在页面显示
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts", departments);
// 转到添加页面
return "emp/add";
}
(1)使用@GetMapping("/emp")
发送emp
+GET
请求,返回"emp/add"
页面
(2)为了方便在添加页面上部门信息选项的显示,在这里先将部门departments
查询出来存储到Model
中
3. 在add.html
页面配置每个添加的信息项
4. 配置添加
按钮,将表单设置成emp
+POST
请求
5. 在EmployeeController
中定义addEmp
方法,发送emp
+POST
请求,调用EmployeeDao.save
方法保存添加的员工,添加完成后重定向到list.html/
页面
具体代码:
add.html
:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<!-- 设置成emp+POST方式 -->
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" >
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="zhangsan@Lemon.com" >
</div>
<div class="form-group">
<label>Gender</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" >
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0" >
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<!-- 显示每个部门,提交的是部门的id -->
<option th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="2020-6-14" >
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</main>
(1)将表单设置成emp
+POST
方式:<form th:action="@{/emp}" method="post">
(2)对于部门选项,在页面上显示部门的名称,提交时使用部门id进行提交:
<option th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
其中depts
是EmployeeController
中的方法保存在Model
中的数据
(3)添加按钮:<button type="submit" class="btn btn-primary">添加</button>
EmployeeController
:
@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println("保存的员工的信息:" + employee);
// (3)调用dao方法保存添加的员工
employeeDao.save(employee);
return "redirect:/emps";
}
(1)使用@PostMapping("/emp")
发送emp
+POST
请求,使用employeeDao.save(employee)
来保存添加的员工信息
(2)添加完成后要求仍然返回到list,html
页面,但是不能直接return "emp/list"
,这样的话数据不会更新,可以使用重定向
/转发
。
(3)这里使用重定向到/emps
请求:"redirect:/emps"
,而/emps
请求是list
方法的请求,会返回list.html
页面:return "emp/list";
(4)注意:SpringMVC自动将请求参数和入参对象的属性进行绑定,要求请求参数的名字和JavaBean入参对象的属性名相同
(5)对于自定义日期格式:可以在主配置文件application.properties
中添加配置:spring.mvc.format.date=yyyy-MM-dd
四、修改员工
1. 在list.html
页面配置编辑
按钮,发送/emp/{id}+GET
请求
2. 在EmployeeController
中定义toEditPage
方法,发送emp/{id}+GET
请求来到修改页面/templates/emp/update.html
具体代码:
list.html
:
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
(1)使用拼串的方式加上当前遍历到的emp的id值:th:href="@{/emp/}+${emp.id}"
EmployeeController
:
@GetMapping("/emp/{id}")
public String toEditPage(@PathVariable("id") Integer id, Model model){
// 查询出当前员工
Employee employee = employeeDao.get(id);
// 保存查询到的员工数据
model.addAttribute("emp", employee);
// 页面要显示所有的部门列表
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts", departments);
// 回到change修改页面
return "emp/change";
}
(1)因为要从传入的参数获取id值,才能针对具体某个员工进行修改,所以使用@PathVariable("id")
注解
3. 来到update.html
页面(和add
页面类似),需要回显信息
4. 配置修改
按钮,将表单设置成emp
+PUT
请求
5. 在EmployeeController
中定义updateEmployee
方法,发送emp
+PUT
请求,调用EmployeeDao.save
方法保存添加的员工,修改完成后重定向到list.html/
页面
具体代码:
update.html
:
<form th:action="@{/emp}" method="post">
<!-- 要发送PUT请求,修改员工数据 -->
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" th:value="${emp.id}">
<div class="form-group">
<label>LastName</label>
<!-- TODO 回写内容 -->
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp.lastName}">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="zhangsan@Lemon.com" th:value="${emp.email}">
</div>
<div class="form-group">
<label>Gender</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp.gender==1}">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp.gender==0}">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<!-- TODO 显示每个部门,提交的是部门的id -->
<option th:selected="${dept.id==emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
</div>
<button type="submit" class="btn btn-primary">修改</button>
</form>
(1)因为在toEditPage
方法中根据发送的id信息获取到了该员工,并保存在了Model
中:model.addAttribute("emp", employee);
,所以只需要使用th:value="${emp.属性名}"
即可回显查询到的员工信息
(2)对于gender
属性,使用
th:checked="${emp.gender==1}"
来进行判断:如果查询到的员工性别为男,则选中。
(3)对于department
属性,使用
th:selected="${dept.id==emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}"
th:each="dept:${depts}"
遍历保存在Model
中的depts
所有部门信息,得到每一个部门dept
th:value="${dept.id}"
获得遍历到的当前部门idth:selected="${dept.id==emp.department.id}"
判断当前遍历到的部门是否和要编辑查询到的emp
的部门id相同th:text="${dept.departmentName}"
:如果判断成立,则回显部门名称
(4)对于birth
属性,使用
th:value="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"
将编辑查询到的emp.birth
属性使用dates.format
来进行日期格式化
(5)对于id
属性,并不需要显示在页面上,所以使用
<input type="hidden" name="id" th:value="${emp.id}">
来进行隐式输出
(6)创建一个input
项,来发送PUT
请求,name="_method"
这个名称不能变!:<input type="hidden" name="_method" value="put">
(7)<button type="submit" class="btn btn-primary">修改</button>
:修改按钮
(8)注意:一定要在主配置文件中配置spring.mvc.hiddenmethod.filter.enabled=true
,否则修改后还是发送POST
请求,而不是(6)中指定的PUT
请求
EmployeeController
:
@PutMapping("/emp")
public String updateEmployee(Employee employee){
System.out.println("修改的员工数据:" + employee);
// 修改后要保存,如果有id,map重新put进去
employeeDao.save(employee);
return "redirect:/emps";
}
(1)使用@PutMapping("/emp")
发送emp
+PUT
请求,使用employeeDao.save(employee)
来保存信息
(2)使用"redirect:/emps"
来重定向到list.html
页面
(3)注意:如果要把add.html
和update.html
合并在一起,那么可以加上一个判断th:if="${emp!=null}"
来判断当前是修改还是添加,因为在修改时需要先查询id的员工信息,存储在Model
中,所以emp
不为空;而添加时不需要查询,emp
为空。
五、删除员工
1. 在list.html
页面配置删除
按钮,发送/emp/{id}+DELETE
请求
2. 在EmployeeController
中添加deleteEmployee
方法,发送emp/{id}+DELETE
请求,调用EmployeeDao.delete
方法删除指定id的员工,删除后重定向到list.html/
页面
3. 美化删除按钮
具体代码:
list.html
:
<div>
<div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<!-- 将list.html中的员工添加按钮设置可以跳转到添加页面 -->
<h2><a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">员工添加</a></h2>
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
<!-- 省略 -->
</main>
<form id="deleteEmpForm" method="post">
<input type="hidden" name="_method" value="delete"/>
</form>
</div>
</div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.16.1/dist/popper.js}"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.5.0/js/bootstrap.js}"></script>
<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}"></script>
<script>
feather.replace()
</script>
<script>
$(".deleteBtn").click(function () {
$("#deleteEmpForm").attr("action", $(this).attr("del_uri")).submit();
return false;
})
</script>
(1)如果使用下面的配置,删除样式会挤到下一行去
<form th:action="@{/emp/}+${emp.id}" method="post">
<input type="hidden" name="_method" value="delete"/>
<button type="submit" class="btn btn-sm btn-danger">删除</button>
</form>
(2)如果样式丢失,要将三个script
标签使用th:src="@{...}"
替换掉原生属性
th:src="@{/webjars/jquery/3.3.1/jquery.js}"
th:src="@{/webjars/popper.js/1.16.1/dist/popper.js}"
th:src="@{/webjars/bootstrap/4.5.0/js/bootstrap.js}"
EmployeeController
:
@DeleteMapping("/emp/{id}")
public String deleteEmployee(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
(1)使用@DeleteMapping("/emp/{id}")
发送emp/{id{
+DELETE
请求,使用employeeDao.delete(id)
来保存信息
(2)使用"redirect:/emps"
来重定向到list.html
页面