# 快速上手 SpringBoot
# SpringBoot 简介
SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的用来简化 Spring 应用的初始搭建以及开发过程
# SpringBoot 入门程序
| @RestController |
| @RequestMapping("/books") |
| public class BootController { |
| @GetMapping |
| public String geiById(){ |
| System.out.println("springboot is running..."); |
| return "springboot is running..."; |
| } |
| } |
# SpringBoot 官网
- 国外:https://start.spring.io/
- 国内:https://start.aliyun.com/
# Spring 和 SpringBoot 区别
# Spring 程序缺点
- 依赖设置繁琐
- 配置繁琐
# SpringBoot 优点
- 起步依赖(简化依赖配置)
- 自动配置(简化常用工程相关配置)
- 辅助功能(内置服务器)
# SpringBoot 小结
- 开发 SpringBoot 程序要继承 spring-boot-starter-parent
- spring-boot-starter-parent 中定义了若干个依赖管理
- 继承 parent 模块可以避免多个依赖使用相同技术时候出现依赖版本冲突
- 继承 parent 的形式也可以采用引入依赖的形式实现效果
# starter
SpringBoot 中常见项目名称,定义了当前项目使用的所有依赖坐标,以达到减少依赖配置的目的
# SpringBoot(starter)小结
- 开发 SpringBoot 程序需要导入坐标时通常导入对应的 starter
- 每个不同的 starter 根据功能不同,通常包含多个依赖坐标
- 使用 starter 可以实现快速配置的效果,达到简化配置的目的
# SpringBoot 工程引导类
- 启动方式
- SpringBoot 的引导类是 Boot 工程的执行入口,运行 main 方法就可以启动项目
- SpringBoot 工程运行后初始化 Spring 容器,扫描引导类所在包加载 Bean
| @SpringBootApplication |
| public class Springboot01quickstartApplication { |
| public static void main(String[] args){ |
| SpringApplication.run(Springboot01quickstartApplication.class,args); |
| } |
| } |
# 辅助功能(内嵌 tomcat)
# 内置服务器分类
# tomcat 默认
apache 出品,粉丝多,应用面广,负载了若干较重组件
# jetty
更轻量级,负载性能远不及 tomcat
# undertow
负载性能勉强跑赢 tomcat
# REST 开发
# REST 简介
REST,表现形式状态转换
# 传统风格资源描述
http://localhost/user/getById?id=1
http://localhost/user/saveUser
# REST 风格描述形式
http://localhost/user/1
http://localhost/usre
# 优点
- 隐藏资源的访问行为,无法通过地址得知对资源做的何种操作
- 书写简化
# 按照 REST 风格访问资源时使用行为动作区分对资源做了何种操作
http://localhost/users 查询全部用户信息用 GET(查询)
http://localhost/users/1 查询指定用户信息 GET(查询)
http://localhost/users 添加用户信息 POST(新增 / 保存)
http://localhost/users 修改用户信息 PUT(修改 / 更新)
http://localhost/users/1 删除用户信息 DELETE(删除)
# 入门案例
# @RequestMapping
- 类型:方法注解
- 位置:SpringMVC 控制器方法定义
- 作用:设置当前控制器方法请求路径
- 属性:
- value:请求访问路径
- method:http 请求动作,标准动作(GET/POST/PUT/DELETE)
| @RequestMapping(value = "/users",methods = RequestMethod.POST) |
| @ResponseBody |
| public String save(@RequestBody User user){ |
| return "{'moudle':'user save'}"; |
| } |
# @PathVariable
- 类型:形参注解
- 位置:SpringMVC 控制器方法形参前
- 作用:绑定路径参数与处理器方式行参间关系,要求路径参数名一一对应
| @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE) |
| @ResponseBody |
| public String delete(@PathVariable Integer id){ |
| return "{'module':'user delete'}"; |
| } |
# @RequestBody @RequestParam @PathVariable
# 区别
- @RequestParam 用于接收 url 地址传参或表单传递
- @RequestBody 用于接收 json 数据
- @PathVariable 用于接收路径参数,使用(参数名称)描述路径参数
# 应用
- 后期开发中,发送请求超过一个时,以 json 格式为主,@RequestBody 应用较广
- 如果发送非 json 格式数据,选用 @RequestParam 接收请求参数
- 采用 RESTful 进行开发,当参数较少时,例如一个,可以使用 @PathVariable 接收路径请求变量,通常用于传递 id 值
# RESTful 快速开发
# @RestController
- 类型:类注解
- 基于 SpringMVC 的 RESTful 开发控制器类定义在上方
- 设置当前控制器类为 RESTful 风格,等同于 @Controller 和 @ResponseBody 两个注解组合功能
| @RestController |
| public class BookController { |
| } |
# @GetMapping @PostMapping @PutMapping @DeleteMapping
- 类型:方法注解
- 位置:基于 SpringMVC 的 RESTful 开发控制器方法定义上方
- 作用:设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如 @GetMapping 对用 GET 请求
- 属性:
- value:请求访问路径
| @GetMapping("/{id}") |
| public String getById(@PathVariable Integer id){ |
| return "{'moudle':book getById}"; |
| } |
# 基础配置
# 复制工程
# 原则
- 保留工作基础结构
- 抹掉原始工程痕迹
# 属性配置
# 修改服务器端口
SpringBoot 默认配置文件 application.properties,通过键值对配置对应属性
# 关闭运行日志图标(banner)
| spring.main.banner-mode=off |
# 设置日志相关
# SpringBoot 提供了多种属性配置方式
# application.properties
# application.yml
# SpringBoot 配置文件加载顺序
application.properties > application.yml > application.yaml
常用 application.yml 不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留
# yaml
# 简介
yaml 是一种数据序列化格式
# 优点
- 容易阅读
- 容易与脚本语言交互
- 以数据为核心,重数据轻格式
# yaml 文件扩展名
.yml
.yaml
# yaml 语法
- 大小写敏感
- 属性层次关系使用多行描述,每行结尾使用冒号结束
- 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格
- 属性值前面添加空格(属性名和属性值之间使用冒号 + 空格作为分隔)
- #表示注释
# yaml 数据读取
使用 @Value 读取单个数据,属性名引用方式:$
# yaml 配置文件
| lesson: SpringBoot |
| server: |
| port: 80 |
| enterprise: |
| name: baozi |
| age: 18 |
| tel: 1588888888 |
| subject: |
| - Java |
| - 前端 |
| - 大数据 |
# 数据读取
| @RestController |
| @RequestMapping("/books") |
| public class BookController { |
| @Value("${lesson}") |
| private String lessonName; |
| @Value("${server.port}") |
| private int port; |
| @Value("${enterprise.subject}") |
| private String[] subject; |
| } |
# 在配置文件中可以使用属性名引用方式引用属性
| baseDir: /use/local/fire |
| center: |
| dataDir: ${baseDir}/data |
# 属性值中如果出现转义字符,需要双引号包裹
| lesson: "Spring\tboot\nlesson" |
# 封装全部数据到 Eneviroment 对象
| lesson: SpringBoot |
| server: |
| port: 80 |
| enterprise: |
| name: baozi |
| age: 18 |
| tel: 1588888888 |
| subject: |
| - Java |
| - 前端 |
| - 大数据 |
| @RestController |
| @RequestMapping("/books") |
| public class BookController{ |
| @Autowired |
| private Enviroment env; |
| @GetMapping("/{id}") |
| public String getById(@PathVariable Integer id){ |
| System.out.println(env.getProperty("lesson")); |
| System.out.println(env.getProperty("enterprise.name")); |
| System.out.println(env.getProperty("enterprise.subject[0]")); |
| return "Hello Spring Boot"; |
| } |
| } |
# 自定义对象封装指定参数
| datasource: |
| dirver: com.mysql.jdbc.Driver |
| url: jdbc:mysql://localhost:3306/springdatabase |
| username: root |
| password: root |
| @Component |
| @ConfigurationProperties(prefix = "datasource") |
| public class DataSource { |
| private String dirver; |
| private String url; |
| private String username; |
| private String password; |
| } |
# 整合第三方技术
# SpringBoot 整合 Junit
| @SpringBootTest |
| public class SpringBoot07JunitApplicationTests { |
| @Autowired |
| private BookService bookService; |
| @Test |
| public void testSave() { |
| bookService.save(); |
| } |
| } |
- 名称:@SpringBootTest
- 类型:测试类注解
- 位置:测试类定义上方
- 作用:设置 Junit 加载的 SpringBoot 启动类
- 相关属性:classes:设置 SpringBoot 启动类
| @SpringBootTest |
| class SpringBootJunitApplicationTests{} |
| @SpringBootTest(classes = SpringBoot05JunitApplication.class) |
| class Springboot07JunitApplicationTests{} |
# SpringBoot 整合 MyBatis
# 创建新模块,选择 Spring 初始化,并配置相关基础信息
# 选择当前模块使用的技术集(Mybatis,MySQL)
# 设置数据源参数
| spring: |
| datasource: |
| driver-class-name: com.mysql.jdbc.Driver |
| url: jdbc:mysql://localhost:3306/database |
| username: root |
| password: root |
# 定义数据层接口与映射配置
| @Mapper |
| public interface UserDao{ |
| @Select("select * from user") |
| public List<User> getAll(); |
| } |
# 测试类中注入 dao 接口,测试功能
| @SpringBootTest |
| class SpringBoot08MyBatisApplicationTests{ |
| @Autowired |
| private BookDao bookDao; |
| @Test |
| public void testGetById() { |
| Book book = BookDao.getById(); |
| System.out.println(book); |
| } |
| } |
# 注意事项
# MySQL8.x 驱动强制要求设置时区
- 修改 url,添加 serverTimezone 设定
- 修改 MySQL 数据库配置
| spring: |
| datasource: |
| driver-class-name: com.mysql.cj.jdbc.Driver |
| url: jdbc:mysql://localhost:3306/student?serverTimezone=UTC |
| username: root |
| password: root |
# 驱动过时时,更换为 com.mysql.cj.jdbc.Driver
# SpringBoot 整合 MyBatis-Plus
# 手动添加 SpringBoot 整合 MyBatis-Plus 坐标
| <dependency> |
| <groupId>com.baomidou<groupId> |
| <artifactId>mybatis-plus-boot-starter</artifactId> |
| <version>3.4.3</version> |
| </dependency> |
# 自定义数据层接口映射配置,继承 BaseMapper
| @Mapper |
| public interface UserDao extends BaseMapper<User> { |
| |
| } |
# 整合 Durid
# 导入 Druid 对应 starter
| <dependency> |
| <groupId>com.alibaba</groupId> |
| <artifacId>druid-spring-boot-starter<artifacId> |
| <version>1.2.6</version> |
| </dependency> |
# 变更 Druid 的配置方式
| spring: |
| datasource: |
| druid: |
| driver-class-name: com.mysql.cj.jdbc.Driver |
| url: jdbc:mysql://localhost:3306/database?serverTimezone=UTC |
| username: root |
| password: root |
# 整合 SSMP 案例
# Lombok
# Lombok 基本介绍
- Lombok 是一个 Java 类库,提供了一组注解,简化 pojo 的实体类开发
- lombok 版本由 Spring Boot 提供,无需指定版本
| <dependency> |
| <groupId>org.projectlombok</groupId> |
| <artifacId>lombok</artifacId> |
| <dependency> |
# 常用注解
| @Data |
| public class Book { |
| private Integer id; |
| private String type; |
| } |
为当前实体类在编译期设置对应的 get/set 方法,toString 方法,hashCode 方法,equals 方法等
# 日志 log
| mybatis-plus: |
| configuration: |
| log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
# 数据层开发 —— 分页功能
# 分页操作对象 IPage
| @Test |
| void testGetPage() { |
| IPage page = new Page(1,5); |
| bookDao.selectPage(page,null); |
| } |
# IPage 对象封装的数据
- 数据
- 当前页码值
- 每页数据总量
- 最大页码值
- 数据总量
# 分页操作 MyBatisPlus 拦截器实现
| @Configuration |
| public class MpConfig { |
| @Bean |
| public MyBatisPlusInterceptor mpInterceptor() { |
| |
| MybatisPlusInterceptor mpInterceptor = new MyBatisPlusInterceptor(); |
| |
| mpInterceptor.addInnerInterceptor(new PageationInnerInterceptor()); |
| return mpInterceptor; |
| } |
| } |
# 数据层开发 —— 条件查询
# 使用 QueryWrapper 对象封装查询条件
| @Test |
| void testGetByCondition() { |
| QueryWrapper<Book> qw = new QueryWrapper<Book>(); |
| qw.like("name","Spring"); |
| bookDao.selectList(qw); |
| } |
# 使用 LambdaQueryWrapper 对象封装查询条件
| @Test |
| void testGetCondition() { |
| IPage page = new Page(1,10); |
| LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); |
| lqw.like(Book::getName,"Spring"); |
| bookDao.selectPage(page,lqw); |
| } |
# 支持动态拼写查询条件
| @Test |
| void testGetCondition(){ |
| String name = "Spring"; |
| IPage page = new Page(1,10); |
| LambdaQueryWrapper lqw = new LambdaQueryWrapper<Book>(); |
| lqw.like(String.isNotEmpty(name),Book::getName,"Spring"); |
| bookDao.selectPage(page,lqw); |
| } |
# 业务层开发 —— 快速开发
# 快速开发方案
- 使用 MyBatisPlus 提供有业务层通用接口(IService)与业务层通用实现类(ServiceImpl)
- 在通用类基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的的功能丢失
# 接口定义
| public interface IBookService extends IService<Book> { |
| } |
| public interface IBookService extends IService<Book> { |
| Boolean delete(Integer id); |
| Boolean insert(Book book); |
| Boolean modify(Book book); |
| Book get(Integer id); |
| } |
# 实现类定义
| @Service |
| public class BookServiceImol2 extends ServiceImpl<BookDao,Book> implments IBookService{ |
| } |
# 表现层开发
# 功能测试
| @RestController |
| @ResquestMapping("/books") |
| public class BookController { |
| @Autowired |
| private IBookService bookService; |
| @GetMapping |
| public List<Book> getAll() { |
| return bookService.list(); |
| } |
| } |
# 表现层接口开发
| @RestController |
| @RequestMapping("/books") |
| public class BookController { |
| @Autowired |
| private IBookService bookService; |
| |
| @PostMapping |
| public Boolean save(@RequestBody Book book){ |
| return bookService.save(book); |
| } |
| @PutMapping |
| public Boolean update(@RequestBody Book book){ |
| return bookService.modify(book); |
| } |
| @DeleteMapping("/{id}") |
| public Boolean delete(@PathVariable Integer id){ |
| return bookService.delete(id); |
| } |
| @GetMapping("/{currentPage}/{pageSize}") |
| public List<Book> getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){ |
| return bookService.getPage(currentPage,pageSize).getRecords(); |
| } |
| } |
# RESTful 制作表现层接口
# 查询:GET
| @GetMapping |
| public List<Book> getAll() { |
| return bookService.list(); |
| } |
| @GetMapping("{id}") |
| public Book getById(@PathVariable Integer id){ |
| return bookService.getById(id); |
| } |
| @GetMapping("{currentPage}/{pageSize}") |
| public IPage<Book> getPage(@PathVariable Integer currentPage,@PathVariable Integer pageSize){ |
| IPage<Book> page = new Page<Book>(currentPage,pageSize); |
| return bookService.page(page); |
| } |
# POST:新增
| @PostMapping |
| public Boolean save(@RequestBody Book book){ |
| return bookService.save(book); |
| } |
# DELETE:删除
| @DeleteMapping("{id}") |
| public Boolean delete(@PathVariable Integer id){ |
| return bookService.removeById(id); |
| } |
# PUT:修改
| @PutMapping |
| public Boolean update(@RequestBody Book book){ |
| return bookService.updateById(book); |
| } |
# 接收参数
- 实体数据:@RequestBody
- 路径变量:@PathVariable
# 表现层消息一致性处理
# 数据格式
| { |
| "flag": true, |
| "data": { |
| "id": 1, |
| "name": "baozi", |
| "type": "正常" |
| } |
| } |
# 设计表现层模型类(前后端数据协议)
| @Data |
| public class R{ |
| private Boolean flag; |
| private Object data; |
| public R() {} |
| public R(Boolean flag){ |
| this.flag = flag; |
| } |
| public R(Boolean flag,Object data){ |
| this.flag = flag; |
| this.data = data; |
| } |
| } |
# 表现层统一返回值类型结果
| @RestController |
| @RequestMapping("/books") |
| public class BookController { |
| @Autowired |
| private IBookService bookServie; |
| @PostMapping |
| public R save(@RequestBody Book book){ |
| return new R(bookService.save(book)); |
| } |
| @PutMapping |
| public R update(@RequestBody Book book){ |
| return new R(bookService.updateById(book)); |
| } |
| } |
# 前后端协议联调
# 前端发送异步请求,调用后端接口
# 列表功能
| getAll () { |
| axios.get("/books").then((res) => { |
| this.dataList = res.data.data; |
| }) |
| } |
# 新增功能
1 弹出添加窗口
| handleCreate () { |
| this.dialogFormVisible = true; |
| } |
2 清除数据
| |
| resetForm () { |
| this.formData = {}; |
| } |
| |
| handleCreate () { |
| this.dialogFormVisible = true; |
| this.resetForm(); |
| } |
3 添加
| handleAdd() { |
| |
| axios.post("/books",this.formData).then((res)=>{ |
| |
| if(res.data.flag) { |
| this.dialogFormVisible = false; |
| this.$message.success("添加成功"); |
| } else { |
| this.$message.error("添加失败"); |
| } |
| }).finally(()=>{ |
| this.getAll(); |
| }); |
| } |
4 取消添加
| cancel() { |
| this.dialogFormVisible = false; |
| this.$message.info("取消添加"); |
| } |
# 删除功能
| handleDelete(row) { |
| this.$confirm("是否确定要删除吗","提示",{type:"info"}).then(()=>{ |
| axios.delete("/books/" + row.id).then((res)=>{ |
| if(res.data.flag){ |
| this.$message.success("删除成功"); |
| } else { |
| this.$message.error("删除失败"); |
| } |
| }).finally(()=>{ |
| this.getAll(); |
| }); |
| }).catch(()=>{ |
| this.$message.info("取消删除"); |
| }); |
| }, |
# 修改功能
1 弹出修改框
| handleUpdate(row) { |
| axios.get("/bboks/" + row.id).then((res)=>{ |
| if(res.data.flag){ |
| this.formData = res.data.data; |
| this.dialogFormVisible4Edit = true; |
| } else { |
| this.$message.error("数据同步失败,自动刷新"); |
| } |
| }); |
| } |
2 加载修改后的数据
| |
| handleEdit() { |
| axios.put("/books",this.formData).then((res)=>{ |
| if(res.data.flag){ |
| |
| this.dialogFormVisible4Edit = false; |
| this.$message.success("修改成功"); |
| } else { |
| this.$message.error("修改失败"); |
| } |
| }).finally(()=>{ |
| |
| this.getAll(); |
| }) |
| }, |
# 业务消息一致性处理(异常)
| @RestControllerAdvice |
| public class ProjectExceptionAdvice { |
| @ExceptionHandler(Exception ex){ |
| |
| |
| |
| ex.printStackTrace(); |
| return new (false,null,"系统错误,请稍后再试"); |
| } |
| } |
# 分页功能
1 el 分页组件
| |
| <div class="pagination-container"> |
| <el-pagination |
| class="pagiantion" |
| @current-change="handleCurrentChange" |
| :current-page="pagination.currentPage" |
| :page-size="pagination.pageSize" |
| layout="total, prev, pager, next, jumper" |
| :total="pagination.total"> |
| </el-pagination> |
| </div> |
2 定义分页组件所需数据
| data: { |
| pagination: { |
| currentPage: 1, |
| pageSize: 10, |
| total: 0, |
| } |
| } |
3 分页查询与切换页码值
| |
| getAll() { |
| axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res)=>{ |
| this.pagination.currentPage = res.data.data.current; |
| this.pagination.pageSize = res.data.data.size; |
| this.pagination.total = res.data.data.total; |
| |
| |
| this.dataList = res.data.data.records; |
| }) |
| }, |
| |
| |
| handleCurrentChange(currentPage) { |
| this.pagination.currentPage = currentPage; |
| this.getAll(); |
| }, |
4 删除功能维护(当前页码值 > 最大页码值)
| @GetMapping("{currentPage}/{pageSize}") |
| public R getPage(@PathVariable Integer currentPage,@PathVariable Integer pageSize){ |
| IPage<Book> page = new Page<Book>(currentPage,pageSize); |
| if(currentPage > page.getPages()){ |
| return new R(true,bookService.page(page)); |
| } |
| return new R(true,bookService.page(page)); |
| } |
# 条件查询
1 查询条件数据封装
| pagination: { |
| currentPage: 1, |
| pageSize:5, |
| total:0, |
| type: "", |
| name: "", |
| description: "" |
| } |
2 页面数据绑定
| <div class="filter-container"> |
| <el-input placeholder="图书类别" v-model="pagination.type" style="width: 200px;" class="filter-item"></el-input> |
| <el-input placeholder="图书名称" v-model="pagination.name" style="width: 200px;" class="filter-item"></el-input> |
| <el-input placeholder="图书描述" v-model="pagination.description" style="width: 200px;" class="filter-item"></el-input> |
| <el-button @click="getAll()" class="dalfBut">查询</el-button> |
| <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button> |
| </div> |
3 阻止数据成为 get 请求发送的数据
| getAll() { |
| param = "?type="+this.pagination.type; |
| param += "&name="+this.pagination.name; |
| param += "&description="+this.pagination.description; |
| axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then((res)=>{ |
| this.pagination.currentPage = res.data.data.current; |
| this.pagination.pageSize = res.data.data.size; |
| this.pagination.total = res.data.data.total; |
| |
| this.dataList = res.data.data.records; |
| }) |
| }, |
4 Controller 接收参数
| @GetMapping("{currentPage}/{pageSize}") |
| public R getPage(@PathVariable Integer currentPage,@PathVariable Integer pageSize,Book book){ |
| System.out.println(book); |
| IPage<Book> page = new Page<Book>(currentPage,pageSize); |
| if(currentPage > page.getPages()){ |
| return new R(true,bookService.getPage(page,book)); |
| } |
| return new R(true,bookService.getPage(page,book)); |
| } |
5 业务层接口功能开发
| public interface IBookService extends IService<Book> { |
| IPage<Book> getPage(IPage<Book> page, Book book); |
| } |
| @Service |
| public class BookService2Impl extends ServiceImpl<BookDao, Book> implements IBookService { |
| @Autowired |
| private BookDao bookDao; |
| @Override |
| public IPage<Book> getPage(IPage<Book> page, Book book) { |
| LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); |
| lqw.like(Strings.isNotEmpty(book.getType()),Book::getType,book.getType()); |
| lqw.like(Strings.isNotEmpty(book.getName()),Book::getName,book.getName()); |
| lqw.like(Strings.isNotEmpty(book.getDescription()),Book::getDescription,book.getDescription()); |
| return bookDao.selectPage(page,lqw); |
| } |
| } |