说明:SpringBoot一天速成不是口号,而在于您的基础。
1.此演示目的就是为了让大家速成。口号:“快快快狠”。
2.具备半年以上"SSM框架+Maven"实战经验的开发人员
3.跟着此系列博文《SpringBoot一天速成》练习一遍
4.这套演练包括工程的:持久层、服务层、web层,采用Intellej idea工具。
5.所有源码和资料免费提供给读者,需要的留言。
6.笔者将实践过程中遇到的问题与大家分享,让大家少走弯路。(请阅读注释部分)
SpringBoot是干嘛的?“简化开发,独立运行”,瞄准的目标:微服务。下面是官方原话:
Spring Boot makes it easy to create stand-alone, production-grade Spring based
Applications that you can "just run".
We take an opinionated view of the Spring platform and third-party libraries
so you can get started with minimum fuss. Most Spring Boot applications need very
little Spring configuration.
*====>>> 接着第1集,此演练开始编写Web层内容:Controller和视图模板thymeleaf(替代jsp)*……
声明:@author:拈花为何不一笑,“这是一套演练对于细节方面,需要读者自己完善。”
Web层完成后的效果图(包涵笔者拙劣的.css)
要完成以上功能,需要编写以下代码
-
pom.xml引入springmvc和thymeleaf(当然也可以在创建工程时使用Spring Initailizr初始化这两模块)
<!-- 引入spring-boot-starter-web,那么就提供了SpringMVC支持了 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 视图模板使用thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
打开maven依赖,查看下spring-boot-starter-web依赖了哪些jar,如图:
再看看thymeleaf(目前这个模板的性能是相对比较差的)依赖了哪些jar,如下图:
2.在src/main/resources目录下分别创建两个目录:templates和static
入口类
===>>> Springboot4Application.java
package org.it.springboot4;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* SpingBoot(Spring、SpringMVC) + Mybatis 演练
* 入口类,启动内置Tomcat服务器,初始化等。
* 配置好文件后,启动服务器报ERROR:
* Warning:java: 无法找到类型 'org.junit.jupiter.api.extension.ExtendWith' 的注释方法 'value()':
* 找不到org.junit.jupiter.api.extension.ExtendWith的类文件
* 解决方案: 右键-->pom.xml-->Maven-->Reimport即可解决此问题
*/
@MapperScan("org.it.springboot4.mapper")//扫描包(Mybatis对应的mapper包)
@SpringBootApplication
public class Springboot4Application {
public static void main(String[] args) {
SpringApplication.run(Springboot4Application.class, args);
}
}
2.1 templates中放thymeleaf模板文件(.html文件),这个模板的数据通常来源于Controller
响应的数据
2.2 static目录中放.js和.css及图片之类的数据
2.3 编写SpringMVC控制器EmpController.java(位置:org.it.springboot4.controller)
源码如下:
package org.it.springboot4.controller;
import com.sun.org.apache.xpath.internal.operations.Mod;
import org.it.springboot4.entity.Emp;
import org.it.springboot4.entity.custom.EmpCustom;
import org.it.springboot4.service.EmpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.rmi.MarshalledObject;
import java.util.List;
import java.util.Map;
/**
*演示增删改查
*/
@RestController
@RequestMapping("/emp")
public class EmpController {
private Logger empLog = LoggerFactory.getLogger(EmpController.class);
@Autowired
private EmpService empService;
//访问url: http://localhost:8080/emp/3
@GetMapping("/{id}")
public ModelAndView findEmpById(@PathVariable("id") Integer empno){
ModelAndView modelAndView = new ModelAndView();
Emp emp = empService.getEmpById(empno);
modelAndView.addObject("emp",emp);
modelAndView.setViewName("emp");//springboot中thymeleaf默认后缀.html,服务器转发至/emp.html中
return modelAndView;
}
//1.此方法getEmpByDeptnoSal对应的sql,还可以查询员工列表信息(当不含任何查询条件时),所以这里就不写员工列表方法了
//2.在templates目录下创建模板empList.html文件,具体内容参阅empList.html
@GetMapping("/empList")
public ModelAndView findEmpList(EmpCustom empCustom){
ModelAndView modelAndView = new ModelAndView();
//这里使用Map来替换javaBean(比如Emp),用法有点类似于ibtis存储过程或jdbc存储过程 Map作为既作为参数又同时存储返回的结果数据,
// 这里是一个个的Map.Entry对象,是一种键值对数据,其实返回的javaBean也是通过成员属性来存储值的,键是属性名值是值本身。.
List<Map> emps = empService.getEmpByDeptnoSal(empCustom);
modelAndView.addObject("emps",emps);
modelAndView.setViewName("empList");
return modelAndView;
}
@GetMapping("/deptnosal")//此方法虽然是在restful风格之下,但是仍然可以混用传统Http请求方式,如/deptnosal?deptno=xxx&sal=yyy
//请在浏览器地址栏输入http://localhost:8080/emp/deptnosal?dname=RESEARCH&sal=6600(DB中的数据,如有需要可分享sql脚本给读者)
//与前端表单进行参数绑定(即映射到EmpCustom中)时,无需要包装类,而List参数绑定需要包装类包装
public List<Map> findEmpByDeptnoSal(EmpCustom empCustom){
List<Map> list = empService.getEmpByDeptnoSal(empCustom);
return list;
}
//添加员工页面
@GetMapping("/addEmpUI")//注解漏掉不写,或者写错成@PostMapping都会报String不能转化成Integer异常
public ModelAndView addEmpUI(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("addEmpUI");
return modelAndView;
//采用String 返回return "forward:/addEmpUI.html"; json化无法进行服务器转发,因为类上标的是@RestController
}
//添加一名新员工
@PostMapping("/addEmp")//玩一个工具PostMan,模拟视图层的Http请求,现在禁用了
public ModelAndView addEmp(Emp emp){
ModelAndView modelAndView = new ModelAndView();
Integer num = 0;
if(emp != null){
num = this.empService.insertEmp(emp);
}
//modelAndView.addObject("iRecord",num);//设置重定向所带参数,供其它业务使用
modelAndView.setViewName("redirect:/emp/empList");//重定向,查询员工列表
return modelAndView;
}
//创建并映射Emp,由mysql数据库生成的empno映射到当前对象e.empno中,这种用法通常用于级联增加或删除。
@GetMapping("/addGetEmp")
@ResponseBody
public Emp addGetEmp(Emp emp){
//简单模拟前端数据
//empname,hiredate,job,mgr,sal,comm,deptno
Emp e = new Emp();
e.setEname("刘宇夫");
e.setHiredate(java.sql.Date.valueOf("2016-3-2"));
e.setJob("SALES");
e.setMgr(3);
e.setSal(6500f);
e.setComm(200f);
e.setDeptno(20);
//创建并映射Emp,由mysql数据库生成的empno映射到当前对象e.empno中
this.empService.insertGetEmp(e);
return e;
}
//删除员工(对于增删改,mybatis默认会返回影响表的记录条数)
//由于使用Map作为参数绑定,黑盒子效果,没有empno键: http://localhost:8080/emp/deleteEmp?empno=8 删除不成功
@GetMapping("/deleteEmp")
public ModelAndView deleteEmp(@RequestParam Map paramMap){//Map参数绑定,莫要忘记@RequestParam,否则绑定不成功
ModelAndView modelAndView = new ModelAndView();
//如果没有指定删除条件,那是万万不能执行删除DB中的数据的,除非数据大清洗即便如此还要必须先进行备份。(谨慎!切记!)
//使用Map作为参数,对外就是个黑盒子,其他人并不清楚传递怎样的参数,不建议使用,只作演示。
if(paramMap != null && paramMap.get("currentEmpno") != null){
empLog.debug("empno:" + paramMap.get("currentEmpno"));
Integer iRecord = this.empService.deleteEmp(paramMap);
//modelAndView.addObject("iRecord",iRecord);设置数据,重定向可以作为参数
modelAndView.setViewName("redirect:/emp/empList");//重定向,查询员工列表
}
return modelAndView;
}
//更新员工页面
@GetMapping("/updateEmpUI/{empno}")
public ModelAndView addEmpUI(@PathVariable("empno") Integer empno){
ModelAndView modelAndView = new ModelAndView();
//查询需要更新的员工信息,展示更新面板updateEmpUI.html中
if(empno != null){
Emp emp = this.empService.getEmpById(empno);
modelAndView.addObject("emp",emp);
modelAndView.setViewName("updateEmpUI");
}
return modelAndView;
}
//更新员工
@PostMapping("/updateEmp")
public ModelAndView updateEmp(Emp emp){
ModelAndView modelAndView = new ModelAndView();
//如果没有需要更新的数据,何必还要访问数据库,直接返回
if(emp != null && emp.getEmpno() != null){
//这里遇到一个异常:笔者大意了在EmpMapper.xml中,拿日期跟空字符串进行了比较
//java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String
//导致下面的更新sql抛出异常
// <if test="hiredate != null and hiredate !=''">,解决方案:去掉 and hiredate !=''
Integer iRecord = this.empService.updateEmp(emp);
//modelAndView.addObject("iRecord",iRecord);
modelAndView.setViewName("redirect:/emp/empList");//重定向,查询员工列表
}
return modelAndView;
}
/**
* 1.类比一下JPA或hibernate它们操作DB的思想是面向对象和对象缓存:先把记录从DB中查询出来映射到对象,再操作对象,
* 当然咯,它们分别还提供了自己的JPQL,HQL以及原生SQL,估计设计之初就想到框架本身在与DB交互时不可能包罗万象。
* 2.mybatis虽然是一个非标准的ORM,它的思想是面向sql和对象缓存:
* 通过sql直接与DB交互,然后借助自身缓存或它提供的二级缓存与Redis之类工具组合实现java对象进行缓存的效果。
* 3.优缺点对象:
* (3.1)mybatis的优点是sql调优方便,更加适应敏捷开发,适合大中小型互联网项目(需求变化大),
* 而hiberante贯彻的是DB皆对象,它的开发效率和可移植性非常强,适应于中小型且需求变化少的企业项目比如OA项目。
* (3.2)mybatis缺点,对高性能sql门槛比较高,hibernate入门门槛比较高且不支持数据库中某些方言,在性能优化方面相对比较差,
* 不论如何hibernate曾经辉煌过且创始人被邀入Sun公司成为其中重要成员之一。
*/
}
2.4编写thymeleaf模板文件(empList.html, emp.html, addEmpUI.html, updateEmpUI.html),这里面包含thymeleaf大部分日常用法。
====>>>empList.html
<!DOCTYPE html>
<!-- 引入thymeleaf命名空间 -->
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<!--简单样式表 -->
<style type="text/css">
td{
background-color: #FFEBCD;
margin-left: 5px;
margin-bottom: 20px;
border: 1px solid blue;
border-collapse: collapse;
}
</style>
<title>员工信息列表</title>
</head>
<body>
<div>
<span>员工列表</span>
<span style="margin-left: 50%;"><a href="/emp/addEmpUI">添加员工</a></span>
</div>
<div>
<!-- th:style="'样式内容'",注意里面有两个单引号,可以是使用冒号。 -->
<table th:style="'border:1px solid blue;'">
<tr>
<th>序列号</th>
<th>员工编号</th>
<th>员工姓名</th>
<th>员工岗位</th>
<th>入职日期</th>
<th>员工月薪</th>
<th>员工补贴</th>
<th>所在部门</th>
<th>操作</th>
</tr>
<!-- thymeleaf list 迭代 -->
<tr th:each="emp,stat:${emps}" >
<td th:text="${stat.index + 1}"></td>
<td><a th:href="@{'/emp/'+${emp.empno}}">[[${emp.empno}]]</a></td>
<td><a th:href="@{'/emp/'+${emp.empno}}">[[${emp.ename}]]</a></td>
<td>[[${emp.job}]]</td>
<!-- 日期格式化 -->
<td>[[${#dates.format(emp.hiredate,'yyyy年MM月dd日')}]]</td>
<td>[[${emp.sal}]]</td>
<td>[[${emp.comm}]]</td>
<td th:switch="${emp.dname}">
<span th:case="RESEARCH">科研部</span>
<span th:case="SALES">销售部</span>
<span th:case="RESEARCH">运维部</span>
<span th:case="TESTING">测试部</span>
<span th:case="ACCOUNTING">财务部</span>
</td>
<td>
<a th:href="@{'/emp/'+${emp.empno}}">查看详情</a> |
<a href="#" th:onclick="'confirmDeleteEmp('+${emp.empno}+')'">删除</a> |
<a th:href="@{'/emp/updateEmpUI/'+${emp.empno}}">更新</a>
</td>
</tr>
</table>
</div>
<!-- js -->
<script>
function confirmDeleteEmp(empno) {
if(confirm('确认删除?'))
{//这里设置的currentEmpno不能更改,是Map指定的key,笔者玩的是Map进行参数绑定
window.location.href="/emp/deleteEmp?currentEmpno=" + empno;
}
}
</script>
</body>
</html>
====>>>emp.html
<!DOCTYPE html>
<!-- 引入thymeleaf命名空间 -->
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<!--简单样式表 -->
<style type="text/css">
table{
border:1px solid blue;
}
td{
background-color: #FFEBCD;
margin-left: 5px;
margin-bottom: 20px;
border: 1px solid blue;
border-collapse: collapse;
}
</style>
<title>员工详情</title>
</head>
<body>
<div>员工详情</div>
<div>
<table>
<tr>
<td><label>员工编号</label></td>
<td th:text="${emp.empno}"></td>
</tr>
<tr>
<td><label>员工姓名</label></td>
<td th:text="${emp.ename}"></td>
</tr>
<tr>
<td><label>员工岗位</label></td>
<td th:text="${emp.job}"></td>
</tr>
<tr>
<td><label>入职日期</label></td>
<td>[[${#dates.format(emp.hiredate,'yyyy年MM月dd日')}]]</td>
</tr>
<tr>
<td><label>员工薪资</label></td>
<td th:text="${emp.sal}"></td>
</tr>
<tr>
<td><label>员工津贴</label></td>
<td th:text="${emp.comm}"></td>
</tr>
</table>
</div>
<div>
<a href="/emp/empList" style="margin-left:150px;"> 返回 </a>
</div>
</body>
</html>
====>>>addEmpUI.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<!--简单样式表 -->
<style type="text/css">
table{
border:1px solid blue;
}
td{
background-color: #FFEBCD;
margin-left: 5px;
margin-bottom: 20px;
border: 1px solid blue;
border-collapse: collapse;
}
</style>
<title>新增员工</title>
</head>
<body>
<div><span>添加员工</span></div>
<div>
<!-- 这里不要使用th:action和th:method,转发过来时报模板解析异常 -->
<form action="/emp/addEmp" method="post" onsubmit="return nonEmpty()">
<table>
<tr>
<td><label for="empno">员工编号</label></td>
<td><input th:name="empno" id="empno" th:disabled="true"></td>
</tr>
<tr>
<td><label for="ename">员工姓名</label></td>
<td><input th:name="ename" id="ename"></td>
</tr>
<tr>
<td><label for="job">员工岗位</label></td>
<td><input th:name="job" id="job"></td>
</tr>
<tr>
<td><label for="hiredate">入职日期</label></td>
<!-- SpringMVC自定义数据类型转换器,这里读者自己实现来支持的格式,默认如下 -->
<td><input th:name="hiredate" id="hiredate" th:placeholder="格式2016-6-5"></td>
</tr>
<tr>
<td><label for="sal">员工薪资</label></td>
<td><input th:name="sal" id="sal"></td>
</tr>
<tr>
<td><label>员工津贴</label></td>
<td><input th:name="comm" id="comm"></td>
</tr>
<tr>
<td><label for="deptno">所在部门</label></td>
<!--演示简单实现,读者自己使用下拉框实现-->
<td><input th:name="deptno" id="deptno" placeholder="10,20,30,40,50其中之一"></td>
</tr>
<tr>
<td colspan="2">
<input th:type="submit" th:value="添加">
<a href="/emp/empList" style="margin-left:150px;"> 返回 </a>
</td>
</tr>
</table>
</form>
</div>
<!-- js ,不采用实体校验(不灵活),直接通过写js来检验非空-->
<script>
function nonEmpty() {
debugger;
var validateValue = true;
//部门非空校验,数据库中数据完整性约束--非空
var deptno = document.getElementById("deptno").value;
console.info(deptno);
if(deptno === null || deptno == ""){
validateValue = false;
document.getElementById("deptno").parentNode.innerHTML="<input name='deptno' id='deptno'><label>deptno不能为空!</label>";
}else{
document.getElementById("deptno").parentNode.innerHTML="<input name='deptno' id='deptno' value='"+deptno+"'>";
}
//名字非空校验
var ename = document.getElementById("ename").value;
if(ename === null || ename == ""){
validateValue = false;
document.getElementById("ename").parentNode.innerHTML="<input name='ename' id='ename'><label>ename不能为空!</label>";
}else{
document.getElementById("ename").parentNode.innerHTML="<input name='ename' id='ename' value='"+ename+"'>";
}
return validateValue;
}
</script>
</body>
</html>
上一张addEmpUI.html中的js调试图,Intellj idea自动提示js函数,本来是取value值的,被提示一误导选择了valueof()函数。调试工具(chrome,firefox,IE都可以调试觉得还是chrome方便一点)。
====>>> updateEmpUI.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<!--简单样式表 -->
<style type="text/css">
table{
border:1px solid blue;
}
td{
background-color: #FFEBCD;
margin-left: 5px;
margin-bottom: 20px;
border: 1px solid blue;
border-collapse: collapse;
}
</style>
<title>更新员工信息</title>
</head>
<body>
<div><span>更新员工信息</span></div>
<div>
<!-- 这里不要使用th:action和th:method,转发过来时报模板解析异常 -->
<form action="/emp/updateEmp" method="post" onsubmit="return nonEmpty()">
<table>
<tr>
<td><label for="empno">员工编号</label></td>
<!-- 注意取值th:value,使用的是thymeleaf来取数据,前缀"th:"不能少-->
<td><input th:name="empno" id="empno" th:readonly="true" th:value="${emp.empno}"></td>
</tr>
<tr>
<td><label for="ename">员工姓名</label></td>
<td><input th:name="ename" id="ename" th:value="${emp.ename}"></td>
</tr>
<tr>
<td><label for="job">员工岗位</label></td>
<td><input th:name="job" id="job" th:value="${emp.job}"></td>
</tr>
<tr>
<td><label for="hiredate">入职日期</label></td>
<!-- SpringMVC自定义数据类型转换器,这里读者自己实现来支持的格式,默认如下 -->
<td><input th:name="hiredate" id="hiredate" th:value="${#dates.format(emp.hiredate,'yyyy-MM-dd')}"></td>
</tr>
<tr>
<td><label for="sal">员工薪资</label></td>
<td><input th:name="sal" id="sal" th:value="${emp.sal}"></td>
</tr>
<tr>
<td><label>员工津贴</label></td>
<td><input th:name="comm" id="comm" th:value="${emp.comm}"></td>
</tr>
<tr>
<td><label for="deptno">所在部门</label></td>
<!--演示简单实现,读者自己使用下拉框实现-->
<td><input th:name="deptno" id="deptno" placeholder="10,20,30,40,50其中之一" th:value="${emp.deptno}"></td>
</tr>
<tr>
<td colspan="2">
<input th:type="submit" th:value="更新">
<a href="/emp/empList" style="margin-left:150px;"> 返回 </a>
</td>
</tr>
</table>
</form>
</div>
<!-- js ,不采用实体校验(不灵活),直接通过写js来检验非空-->
<script>
function nonEmpty() {
debugger;
var validateValue = true;
//部门非空校验,数据库中数据完整性约束--非空
var deptno = document.getElementById("deptno").value;
console.info(deptno);
if(deptno === null || deptno == ""){
validateValue = false;
document.getElementById("deptno").parentNode.innerHTML="<input name='deptno' id='deptno'><label>deptno不能为空!</label>";
}else{
document.getElementById("deptno").parentNode.innerHTML="<input name='deptno' id='deptno' value='"+deptno+"'>";
}
//名字非空校验
var ename = document.getElementById("ename").value;
if(ename === null || ename == ""){
validateValue = false;
document.getElementById("ename").parentNode.innerHTML="<input name='ename' id='ename'><label>ename不能为空!</label>";
}else{
document.getElementById("ename").parentNode.innerHTML="<input name='ename' id='ename' value='"+ename+"'>";
}
return validateValue;
}
</script>
</body>
</html>
===>>>大家有没有发现一个问题,就是SpringMVC的四大器件(前端控制器、映射器、处理器适配器和视图解析器),
本人一个都没有配置,全部由SpringBoot来管理了。是不是在Web层给开发人员进行了简化呢?是的!
玩Mybatis的都知道,sql日志是多么的重要,第3集笔者(拈花为何不一笑)将演示slf4j-logback日志的配置和遇到的坑,及热部署、打包等。希望能够帮助大家少走弯路……