SpringBoot
- 基于Spring开发,本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。
- 核心思想:约定大于配置,
- 集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz等等)
微服务架构
单体应用架构
- 将应用中的所有应用服务都封装在一个应用中
- 优点
- 易于开发和测试
- 方便部署
- 需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡即可
- 缺点
- 修改时需要停止整个服务,重新打包部署
- 将每个功能元素独立出来,并将独立出来的功能元素动态组合。只对功能元素进行复制
- 优点
- 节省了调用资源
- 每个功能元素的服务都是一个可替换的、可独立升级的软件代码
第一个SpringBoot程序
快速生成网站:Spring Initializr
- 填写信息 下载jar包
- 在IDEA中导入jar包
在IDEA中新建项目,选择Spring Initializr,输入项目名,项目位置,组名等信息后,点击next
在当前页面勾选Spring Web,以便项目可以在网页上运行
项目结构
controller、dao、service包要建在Application.java的同级目录下
在controller包下建立HelloController.java
package com.example.springboot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//自动装配
@RestController
public class HelloController {
//接口:http://localhost:8080/hello
@RequestMapping("/hello")
public String hello(){
//调用业务,接受前端的参数
return "Hello,World!";
}
}
运行Application.java,运行结果如下
在网页中输入接口http://localhost:8080/hello
自动装配原理
- xxxxAutoConfiguration:向容器中自动配置组件
- xxxxProperties:自动配置属性类,装配配置文件中自定义的内容
SpringBoot所有自动配置都是在启动时扫描并加载spring.factories
,所有的自动配置类都在里面,但不一定生效,需要判断条件是否成立。当导入对应的start时,会生成对应的启动器,启动器使自动装配生效,完成自动配置
- springboot在启动时,从类路径下
/META-INF/spring.factories
获取指定的值 - 将这些自动配置的类导入容器,使自动配置生效,进行自动配置
- 整合JavaEE,解决方案和自动配置的文件都在
spring-boot-autoconfigure-2.7.1.jar
包下,它会将所有需要导入的组件,以类名的方式返回并添加到容器 - 在容器中也存在许多xxxAutoConfiguration的文件(@Bean),这些类给容器中导入了场景需要的所有组件,并自动配置
- 自动配置类免去了手动编写配置文件的工作
SpringBoot配置
配置文件
- application.properties
- 语法结构: key=value
- application.yml(application.yaml)
- 语法结构: key:(空格)value
配置文件的作用:修改SpringBoot自动配置的默认值
YAML
yaml配置:
# 普通的key-value
key: value
# 对象
student:
name: name
age: 3
# 行内写法
teacher: {name: name,age: 3}
# 数组
pets:
- cat
- dog
yaml可以直接给实体类赋值
//Person.java
package com.example.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "person")//将自动配置信息封装为实体类,将配置文件中的person的属性与实体类中的属性一一对应
public class Person {
private String name;
private Integer age;
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Date birth, Map<String, Object> map, List<Object> list, Dog dog) {
this.name = name;
this.age = age;
this.birth = birth;
this.map = map;
this.list = list;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public List<Object> getList() {
return list;
}
public void setList(List<Object> list) {
this.list = list;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", birth=" + birth +
", map=" + map +
", list=" + list +
", dog=" + dog +
'}';
}
}
//Dog.java
package com.example.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Dog {
private String name;
private Integer age;
public Dog() {
}
public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
person:
name: 张三
age: 23
birth: 2000/01/01
map: {k1: v1,k2: v2}
list:
- code
- music
dog:
name: 秀儿
age: 3
@ConfigurationProperties和@Value对比
@ConfigurationProperties @Value 功能 批量注入配置文件中的属性 手动指定 松散绑定 支持 不支持 SPEL 不支持 支持 JSR303数据校验 支持 不支持 复杂类型封装 支持 不支持
- 松散绑定:在yaml中,last-name和lastName是一样的,-后的字母默认大写
- JSR303数据校验(@Validated):在字段上增加一层过滤器验证,以保证数据的合法性
@Validated常用注解
注解 | 描述 |
---|---|
@Null | 被注释的元素必须为null |
@NotNull | 被注释的元素必须不为null |
@AssertTrue | 被注释的元素必须为true |
@AssertFalse | 被注释的元素必须为false |
@Min(value) | 被注释的元素必须一个字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须一个字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max,min) | 被注释的元素的大小必须指的范围内 |
@Digits(integer,fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内。interger指定整数精度,fraction指定小数精度 |
@Past | 被注释的元素必须一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
多环境配置&配置文件的位置
SpringBoot默认配置文件优先级
- 项目路径下的config文件夹配置文件
- 项目路径下配置文件
- 资源路径下的config文件夹配置文件
- 资源路径下配置文件
当同优先级下的yml和properties都配置了端口,且没有激活其他环境时,默认会使用properties文件中端口
SpringBoot Web开发
需要解决的问题:
- 静态资源导入
- 首页
- jsp,模板引擎Thymeleaf
- 装配扩展SpringMVC
- 增删改查
- 拦截器
- 国际化
静态资源
- 在SpringBoot中处理静态资源的方式
- webjars 映射—>
localhost:8080/webjars
- public,static,/**,resources 映射—>
localhost:8080/
- 优先级:resources>static(默认)>public
- webjars 映射—>
模板引擎
导入依赖
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
将html页面放在resources/templates包下
//IndexController.java
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
//在template目录下的所有页面,只能通过controller跳转 需要模板引擎支持(Thymeleaf)
@Controller
public class IndexController {
@RequestMapping("test")
public String test(Model model){
model.addAttribute("msg","hello");
return "test";
}
}
<!--test.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有的html元素都可以被Thymeleaf替换接管-->
<!--@thymesVar id="msg" type="String"-->
<div th:text="${msg}"></div>
</body>
</html>
这条注释可以消除
th:text=“{msg}”
中msg
爆红的问题
运行Spring03WebApplication
,在浏览器中输入接口[Title](http://localhost:8080/test)
,运行结果如下
Thymeleaf语法
参考博客:Thymeleaf的基本语法 - 简书 (jianshu.com)
关键字 | 功能 | 举例 |
---|---|---|
th:id | 替换id | <input th:id="'XXX'+${collect.id}"/> |
th:text | 文本替换 | <div th:text="${msg}"></div> |
th:utext | 文本替换,可识别html标签 | <div th:utext="${msg}"></div> |
th:object | 替换对象 | <div th:object="${session.user}"/> |
th:each | 属性赋值 | <h3 th:each="user:${users}"></h3> |
th:if | 判断条件 | <a th:if="${userId==collect.userId}"> |
案例:员工管理系统
静态资源:狂神springboot静态资源
创建数据表
实体类
//Employee.java
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
//员工表
@Data
@NoArgsConstructor//创建无参构造方法
public class Employee {
private Integer employeeId;
private String employeeName;
private String email;
private Integer gender;//0为女,1为男
private Department department;
private Date date;
public Employee(Integer employeeId, String employeeName, String email, Integer gender, Department department) {
this.employeeId = employeeId;
this.employeeName = employeeName;
this.email = email;
this.gender = gender;
this.department = department;
//默认创建日期
this.date=new Date();
}
public Employee(String employeeName, String email, Integer gender, Department department) {
this.employeeName = employeeName;
this.email = email;
this.gender = gender;
this.department = department;
this.date=new Date();
}
}
//Department.java
package com.example.dao;
import com.example.pojo.Department;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
//部门Dao
@Repository
public class DepartmentDao {
//模拟数据库中的数据
private static Map<Integer, Department> departmentMap=null;
static {
departmentMap=new HashMap<>();//创建部门表
departmentMap.put(101,new Department(101,"技术部"));
departmentMap.put(102,new Department(102,"财务部"));
departmentMap.put(103,new Department(103,"人事部"));
departmentMap.put(104,new Department(104,"市场部"));
departmentMap.put(105,new Department(105,"法务部"));
}
//获取部门信息
public Collection<Department> getDepartments(){
return departmentMap.values();
}
//通过id获取部门
public Department getDepartmentById(Integer id){
return departmentMap.get(id);
}
}
Dao
//DepartmentDao.java
package com.example.dao;
import com.example.pojo.Department;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
//部门Dao
@Repository//在Spring中配置扫描包地址,然后生成dao层的bean,之后被注入到ServiceImpl中
public class DepartmentDao {
//模拟数据库中的数据
private static Map<Integer, Department> departmentMap=null;
static {
departmentMap=new HashMap<>();//创建部门表
departmentMap.put(101,new Department(101,"技术部"));
departmentMap.put(102,new Department(102,"财务部"));
departmentMap.put(103,new Department(103,"人事部"));
departmentMap.put(104,new Department(104,"市场部"));
departmentMap.put(105,new Department(105,"法务部"));
}
//获取部门信息
public Collection<Department> getDepartments(){
return departmentMap.values();
}
//通过id获取部门
public Department getDepartmentById(Integer id){
return departmentMap.get(id);
}
}
//EmployeeDao.java
package com.example.dao;
import com.example.pojo.Department;
import com.example.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
//员工Dao
@Repository//在Spring中配置扫描包地址,然后生成dao层的bean,之后被注入到ServiceImpl中
public class EmpolyeeDao {
//模拟数据库中的数据
private static Map<Integer,Employee> employeeMap=null;
//员工有所属部门
@Autowired
private DepartmentDao departmentDao;
static {
employeeMap=new HashMap<>();//创建员工表
employeeMap.put(1001,new Employee(1001,"张三","123@email.com",1,new Department(101,"技术部")));
employeeMap.put(1002,new Employee(1002,"李四","1343@email.com",1,new Department(102,"财务部")));
employeeMap.put(1003,new Employee(1003,"刻晴","12213@email.com",0,new Department(105,"法务部")));
employeeMap.put(1004,new Employee(1004,"王五","1134141@email.com",1,new Department(103,"人事部")));
employeeMap.put(1005,new Employee(1005,"可莉","352143@email.com",0,new Department(101,"技术部")));
employeeMap.put(1006,new Employee(1006,"小鹿","1451413@email.com",0,new Department(103,"人事部")));
employeeMap.put(1007,new Employee(1007,"胡强","352143@email.com",1,new Department(104,"市场部")));
}
//主键自增
private static Integer initid=1008;
//增加员工&保存对员工信息的修改
public void saveEmployee(Employee employee){
if(employee.getEmployeeId()==null){
employee.setEmployeeId(++initid);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getDepartmentId()));
employeeMap.put(employee.getEmployeeId(),employee);
}
//查询全部员工信息
public Collection<Employee> getEmployees(){
return employeeMap.values();
}
//通过id查询员工
public Employee getEmployeeById(Integer id){
return employeeMap.get(id);
}
//通过id删除员工
public void deleteEmployeeById(Integer id){
employeeMap.remove(id);
}
}
首页实现
在resources/template下的html文件无法由config类直接访问,需要建立控制类
//IndexController.java
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
}
或者修改config类
//MvcConfig.java
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
页面国际化
国际化乱码问题解决:SpringBoot国际化配置出现乱码问题
配置i18n文件
# login_zh_CN.properties
login.btn=登录
login.password=请输入密码
login.remember=记住密码
login.tip=请登录
login.title=登录界面
login.username=请输入用户名
# login_en_US.properties
login.btn=Sign In
login.password=Please enter your password
login.remember=Remember me
login.tip=please sign in
login.title=Login
login.username=Please enter your username
login.properties为默认语言,可在login_zh_CN.properties
和login_en_US.properties
中任意选择
自定义国际化组件LocaleResolver
//MyLocaleResolver.java
package com.example.config;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocaleResolve implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("language");
Locale locale = Locale.getDefault();//language为空,则使用默认的语言环境
if(!StringUtils.isEmpty(language)){
//zh_CN
String[] split = language.split("_");
//区分国家和地区
locale=new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
在MVcConfig.java
中注册bean
//自定义国际化组件
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolve();
}
修改index.xml
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>[[#{login.title}]]</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" src="img/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label class="sr-only" ></label>
<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only"></label>
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox">[[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(language='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(language='en_US')}">English</a>
</form>
</body>
</html>
登录功能实现
编写控制器LoginController.java
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.util.StringUtils;
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
//判断用户名&密码是否正确
if(!StringUtils.isEmpty(username)&&"123".equals(password)){
//登录成功
return "redirect:/main.html";
}
else{
//登陆失败
model.addAttribute("msg","用户名或密码错误");
return "/index";
}
}
}
不能将@Controller注解换成@RestController,因为@RestController是@Controller+@ResponseBody的结合,返回的是return后面的值(json类型),会导致页面无法正常显示
在MvcConfig.java
中增加视图跳转语句
registry.addViewController("/main.html").setViewName("dashboard");
避免登录成功后跳转至dashboard.html页面时,域名写有用户名和密码。
如果dashboard样式显示错误,可对下方语句进行修改
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
登录拦截器
由于在编写控制器时控制了dashboard.html的页面跳转,在实际使用过程中,只需要输入http://localhost:8080/management/main.html
即可跳转至dashboard界面。因此我们需要增加一个拦截器
编写拦截器LoginHandlerInterceptor
package com.example.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功后,用户会产生session
Object user = request.getSession().getAttribute("loginUser");
if(user==null){
//没有登录
request.setAttribute("msg","没有权限,请先登录!");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;//true放行,false不放行
}
else
return true;
}
}
在MvcConfig
中配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")//拦截的对象
.excludePathPatterns(
"/index.html","/","/user/login",
"/css/*","/js/*","/img/*")//放行的对象;
}
增删改查功能实现
提取公共页面
将dashboard.html
和list.html
中的公共部分(顶部导航栏和侧边栏)提取出来,放入常用资源包commons下的commons.html
中,用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>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">注销</a>
</li>
</ul>
</nav>
<!--sidebar-->
<!--侧边栏-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" th:href="@{/main.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
<polyline points="13 2 13 9 20 9"></polyline>
</svg>
Orders
</a>
</li>
<li class="nav-item ">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
Products
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/employees}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
Customers
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Reports
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
Integrations
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Saved reports</span>
<a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
</a>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Current month
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Last quarter
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Social engagement
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Year-end sale
</a>
</li>
</ul>
</div>
</nav>
将dashboard.html
和list.html
中原先关于顶部导航栏和侧边栏的语句替换为下方代码
仅替换<nav>
标签下的代码即可
<!--顶部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<!--侧边栏-->
<div th:replace="~{commons/commons::sidebar}"></div>
设置选中高亮
在最初的网页中,无论当前处于什么界面,只用“首页”存在高亮,在使用中无法清楚地观察当前的页面。为了解决这一问题,我们可以在对应的html文件中增加参数传递,并在commons.html
中使用th:class="${}"
配合三元运算进行判断,实现高亮状态的切换。
修改dashboard.html
中侧边栏的语句
<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>
其中,main.html
与MvcConfig.java
中dashboard.html
对应的视图跳转名称一致
修改list.html
中侧边栏的语句
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
修改commons.html
中对应代码,代码位置及修改如下
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/employees}">
/employees
为EmployeeController.java
定义的Mapper接口
EmployeeController.java
代码如下package com.example.controller; import com.example.dao.EmpolyeeDao; import com.example.pojo.Employee; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import java.util.Collection; @Controller public class EmployeeController { @Autowired EmpolyeeDao empolyeeDao; @RequestMapping("/employees") public String List(Model model){ Collection<Employee> employees = empolyeeDao.getEmployees(); model.addAttribute("employees",employees); return "employees/list"; } }
展示员工列表
修改list.html
原有的表格代码,代码位置及修改如下
<table class="table table-striped table-sm">
<thead>
<tr>
<th>工号</th>
<th>姓名</th>
<th>电子邮箱</th>
<th>性别</th>
<th>部门信息</th>
<th>入职日期</th>
</tr>
</thead>
<tbody>
<!--@thymesVar id="employees" type="List<com.example.pojo.Employee>"-->
<!--@thymesVar id="employee" type="com.example.pojo.Employee"-->
<tr th:each="employee:${employees}">
<td th:text="${employee.getEmployeeId()}"/>
<td th:text="${employee.getEmployeeName()}"/>
<td th:text="${employee.getEmail()}"/>
<td th:text="${employee.getGender()==0?'女':'男'}"/>
<td th:text="${employee.getDepartment().getDepartmentName()}"/>
<td th:text="${#dates.format(employee.getDate(),'yyyy-MM-dd')}"/>
</tr>
</tbody>
</table>
<td th:text="${employee.getGender()==0?'女':'男'}"/>
将数据表中对于性别的描述(0/1)转换为(女/男)
<td th:text="${#dates.format(employee.getDate(),'yyyy-MM-dd')}"/>
将日期的输出格式进行定义
employees
来源于EmployeeController.java
中的查询方法,代码如下package com.example.controller; import com.example.dao.EmpolyeeDao; import com.example.pojo.Employee; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.util.Collection; @Controller public class EmployeeController { @Autowired EmpolyeeDao empolyeeDao; @RequestMapping("/employees") public String List(Model model){ Collection<Employee> employees = empolyeeDao.getEmployees(); model.addAttribute("employees",employees); return "employees/list"; } }
为了方便后续的增删改操作,我们可以在显示页面增加按钮:添加、删除、修改
首先,我们需要在原有的员工表中增加一列,即在<th>入职日期</th>
后增加如下代码
<th>操作</th>
增加操作列之后,我们需要在表格中增加对应的按钮,即在<td th:text="${#dates.format(employee.getDate(),'yyyy-MM-dd')}"/>
之后增加如下代码
<td>
<a class="btn btn-sm btn-danger">删除</a>
<a class="btn btn-sm btn-primary">编辑</a>
</td>
同时,我们需要在页面上增加“添加员工”按钮,即在<div class="table-responsive">
上方添加如下代码
<a class="btn btn-sm btn-success">添加员工</a>
运行结果
添加员工
在实现员工列表展示时,我们在主页增加了“添加员工”的按钮,我们只需要实现它,就可以实现添加员工的功能。
首先,我们要对“添加员工”按钮的代码进行修改,为其增加一个链接
<a class="btn btn-sm btn-success" th:href="@{/employee}">添加员工</a>
此时,点击“添加员工”按钮后,会向后台发送一个请求/employee
,我们需要在EmployeeController.java
中处理这个请求
@Autowired
DepartmentDao departmentDao;
@GetMapping("/employee")
public String toAddEmployee(Model model){
//查询部门信息
Collection<Department> departments=departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "employees/addEmployee";
}
这个方法可以接受前端的请求,并将页面跳转至addEmployee.html
,addEmployee.html
主体代码如下
<form th:action="@{/employee}" method="post">
<div class="form-group">
<label>姓名</label>
<input type="text" name="employeeName" class="form-control" placeholder="请输入姓名">
</div>
<div class="form-group">
<label>电子邮箱</label>
<input type="text" name="email" class="form-control" placeholder="请输入电子邮箱">
</div>
<div class="form-group">
<label class="form-label">性别</label>
<input type="radio" name="gender" value="0">女
<input type="radio" name="gender" value="1">男
</div>
<div class="form-group">
<label>部门</label>
<!--@thymesVar id="departments" type="java.util.List<com.example.pojo.Department>"-->
<!--@thymesVar id="department" type="com.example.pojo.Department"-->
<select class="form-control" name="department.departmentId" >
<option selected>--请选择部门--</option>
<option th:each="department:${departments}" th:value="${department.getDepartmentId()}">
[[${department.getDepartmentName()}]]
</option>
</select>
</div>
<button class="btn btn-primary">提交</button>
</form>
上述代码为表单代码,其余代码可参考
list.html
,将其中表格代码换成上述代码即可
点击addEmployee.html
中的“提交”按钮,页面会发送一个请求,返回至EmployeeController.java
。此时,我们再编写一个方法,用于增加employee
@PostMapping("/employee")
public String AddEmployee(Employee employee){
empolyeeDao.saveEmployee(employee);
return "redirect:/employees";
}
修改员工信息
与”添加员工“类似,要实现对员工信息的修改,我们只需要实现<a class="btn btn-sm btn-primary">编辑</a>
对应的功能即可
首先,我们需要在改标签下增加一个链接,用于跳转至修改页面
<a class="btn btn-sm btn-primary" th:href="@{/employee/{employeeId}(employeeId=${employee.getEmployeeId()})}">编辑</a>
其他格式的RESTful语句如下
th:href="@{/employee(id=${employee.getEmployeeId()})}" <!--上面这句会用'?'拼接在url后传递参数,在后端处理时需要使用其他方法处理--> th:href="@{/employee/{employee}/(id=${employee.getEmployeeId()})}" th:href="@{'/employee/'+${employee.getEmployeeId()}}"
增加链接之后,我们需要在EmployController.java
中编写对应的方法,以处理请求,实现页面跳转
@GetMapping("/employee/{employeeId}")
//通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中
public String toUpdateEmployee(@PathVariable("employeeId") Integer employeeId, Model model){
Employee employee=empolyeeDao.getEmployeeById(employeeId);
model.addAttribute("employee",employee);
Collection<Department> departments=departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "employees/updateEmployee";
}
如果使用
th:href="@{/employee(id=${employee.getEmployeeId()})}"
,需要使用@RequestParam
进行参数的绑定由于在“添加员工”时,我们已经定义过名为
/employee
的Map映射,因此我们需要修改RESTful语句,以定义不同名称的映射,这里我们将它修改为th:href="@{/employee/(employeeId=${employee.getEmployeeId()})}"
修改
EmployController.java
中的toUpdateEmployee方法@GetMapping("/employee/") public String toUpdateEmployee(@RequestParam("employeeId") Integer employeeId, Model model){ Employee employee=empolyeeDao.getEmployeeById(employeeId); model.addAttribute("employee",employee); Collection<Department> departments=departmentDao.getDepartments(); model.addAttribute("departments",departments); return "employees/updateEmployee"; }
主要区别在于使用了
@RequestParam
替换原来的@pathVariable
关于两种传参方式的选择可以参考:@RequestParam和@PathVariable的用法与区别
使用
@RequestParam
可以实现在修改页面显示数据,但是在后续提交修改时会报错,目前还没有解决这个问题
编写完处理方法后,我们需要编写updateEmployee.html
,即修改页面,主体代码如下
<!--@thymesVar id="employee" type="com.example.pojo.Employee"-->
<form th:action="@{/updateEmployee}" method="post">
<div class="form-group">
<label>姓名</label>
<input type="hidden" name="employeeId" th:value="${employee.getEmployeeId()}">
<input type="text" th:value="${employee.getEmployeeName()}" name="employeeName" class="form-control" placeholder="请输入姓名">
</div>
<div class="form-group">
<label>电子邮箱</label>
<input type="text" th:value="${employee.getEmail()}" name="email" class="form-control" placeholder="请输入电子邮箱">
</div>
<div class="form-group">
<label class="form-label">性别</label>
<input type="radio" th:checked="${employee.getGender()==0}" name="gender" value="0">女
<input type="radio" th:checked="${employee.getGender()==1}" name="gender" value="1">男
</div>
<div class="form-group">
<label>部门</label>
<!--@thymesVar id="departments" type="java.util.List<com.example.pojo.Department>"-->
<!--@thymesVar id="department" type="com.example.pojo.Department"-->
<select class="form-control" name="department.departmentId" >
<option selected >[[${employee.getDepartment().getDepartmentName()}]]</option>
<option th:each="department:${departments}" th:value="${department.getDepartmentId()}"
th:if="${employee.getDepartment().getDepartmentName()}!=${department.getDepartmentName()}">
[[${department.getDepartmentName()}]]
</option>
</select>
</div>
<button class="btn btn-primary">提交</button>
</form>
上方代码仅包含展示的表单,其余代码可参考
addEmployee.html
当我们点击修改页面的提交按钮时,前端会向后端发送请求,要求保存修改并跳转至员工列表页面。我们可以编写控制方法实现请求
@PostMapping("/updateEmployee")
public String UpdateEmployee(Employee employee){
empolyeeDao.saveEmployee(employee);
return "redirect:/employees";
}
当前端发送请求时,后端会执行该方法,保存前端的修改并跳转至员工展示页面
删除员工
相对于增、改、查,删除的功能实现就显得异常简单。
首先,我们需要修改“删除”标签的对应代码,为其增加一个链接
<a class="btn btn-sm btn-danger" th:href="@{/deleteEmployee(employeeId=${employee.getEmployeeId()})}">删除</a>
增加了这个链接之后,当我们点击“删除”时,前端会发送一个请求,此时我们需要在后端接受并处理这个请求,即在EmployeeController.java
中增加一个方法
@GetMapping("/deleteEmployee")
public String deleteEmployee(@RequestParam Integer employeeId){
empolyeeDao.deleteEmployeeById(employeeId);
return "redirect:/employees";
}
至此,删除的功能也实现了
注销
为了方便后续用户切换,我们可以增加一个注销功能。在我们原先的顶部导航栏右侧有一个”注销“按钮,我们只需要对他进行修改,使其点击之后跳转至登录页面即可
<a class="nav-link" th:href="@{/index.html}">注销</a>
“注销”按钮的代码在
commons/commons.html
文件中
整合JDBC
在创建项目时勾选Spring Web、JDBC API
和MySQL Driver
项目创建完成后,编写application.yaml
,在里面配置数据源
spring:
datasource:
username: #{username}
password: #{password}
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
创建controller
包以及JdbcController.java
,读取数据库中的信息
package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class JdbcController {
@Autowired
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql="select * from user";
List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql);
return mapList;
}
}
没有实体类可以使用Map获取数据库中的信息
运行项目,在浏览器中输入http://localhost:8080/userList
,得到如下界面
整合MyBatis
整合包:mybatis-spring-boot-starter
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
在创建项目时选择下列依赖
在application.properties
或application.yaml
中配置datasource相关参数
spring:
datasource:
username: #{username}
password: #{password}
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/#{database-name}?useUnicode=true&characterEncoding=utf-8
在使用时,需要在mapper类前添加@Mapper
注解