1.SpringBoot 项目创建
1.1.IDEA的SpringBoot 项目创建

这里我们的jdk,安装的是8,所以版本选成8.

如果是mvc项目,这里要用web。选这个。

写个入门案例
package com.itheima.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Just Ching
* @create 2022-02-09 6:34
*/
//rest开发模式
@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping
public String getById(){
System.out.println("sb is running");
return "spring boot is running";
}
}




1.1.1 运行环境

1.2.官网创建项目模块

https://start.spring.io/


点击下方generate生成。
下载这个文件,放进IDEA工作空间。就可以了。

1.3.阿里云网址创建项目
这个创建速度十分快。

c
1.4 不联网创建项目,制作。
创建一个全新的模块,maven的pom,copy springboot的pom就行

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
然后new一个全新的类。

,然后就可以用了。
1.5 隐藏,不用修改也不想看的文件
README.md;mvnw.cmd;.gitignore;HELP.md;mvnw;.mvn;*.iml;

1.6 导入依赖的parent。

现在在pom里,导包不用加版本号,它自己选择最适合的。避免冲突。

里面放的都是坐标版本。

点开一看。

里面的依赖管理。

1.7 starter
我们建立项目选的web类型,自动给导入starter。
导入了starter,starter有web要用的包。



1.8 启动的引导类

这句话就是启动spring容器。

这个能扫描到它所在包的bean。别的包扫描不到。

1.9 内置tomcat


内嵌的tomcat核心。

将tomcat执行过程抽取出来,变成一个对象。然后把这个对象交给spring容器去管。

1.9.1 jetty
不用tomcat,用jetty。排除这个,导入jetty。

2.REST开发





发的值少,用路径参数@pathvariable。
值多,用json。
2.1 restful开发案例
不同的提交类型对应不同的方法。

这样可以取到值给自己的方法。
2.2 restful 简化开发

1.直接去掉value,自动识别了属于是。
路径提到类上。
@ResponseBody,也提出来,放在类上,这个类里所有的方法都带有这个注解

上面俩注解可以合并


2.能不能去掉method?

2.boot的配置
2.1 复制工程
复制一份,模块文件。然后修改新工程里的pom文件。

别的文件删了,就剩这俩。

idea里导入全新的模块

然后导入的时候选maven工程。

在IDEA打开后,再打开pom文件。删掉这两句。
或者修改<name>,里的值,改成和模块名一样。


2.2 基础属性配置
里面能配置,是因为pom里导入的spring相关的包。

2.2.1修服务器
必须得有stater web包才能配置。

boot使用的配置文件。 properties,改个key和value。


改成这样,我们启动项目的时候。访问路径就会变化。

前面加前缀,访问。

2.2.2修改下面的图案
banner。导入starter包就能用。

改成自己的图片。

2.2.3日志信息查看
改成debug调试的时候可以看到boot启动的每一步。

改成error,出错才有日志。默认是info级别。

2.3 配置文件类型
格式写的不一样罢了。

2.3.1配置文件加载优先级。

yaml里写配置信息,没有提示。是因为boot现在不认这是配置文件。

所以我们要改的让它认识这是配置文件。


然后点击加进去就行。

然后成功后的样子。

2.3.2 配置文件的小bug
如果添加的时候不识别,那就先建properties文件。再添加。
![]()
2.3.3 yaml 详解
容易阅读。

语法规则

属性前面有空格。

同样的名称只允许出现一次。

数组表示,用-,分开一个数组对象。

2.4 读取配置文件(yml)
controller里读取。用spring的value注入。


读取user的属性。

2.4.1 数据引用问题

引用别的路径
转义字符的使用

这里就转义字符\t,起作用了。制表符。
使用引号包裹的字符串,里面转义的字符可以生效

2.4.2 解决数据一条一条读出来的问题
1.封装全部数据

整一个,所有数据的变量对象。自动装进。

然后用这个对象的getProperty方法获取值。

2.封装自己需要的数据。

比如我们只想要datasource。

创建类,用于封装下面的数据,这个类定义成bean。注意:类里的属性名,与配置文件属性名必须一样
让spring 帮我们去加载数据到对象中,一定 要告诉spring加载这组信息

用@ConfigurationProperties将配置文件的属性绑定。
后面生成对象,会把属性里的值赋值给对象。


3.boot整合第三方技术
目的学会思想:拿到任何第三方技术的时候,怎么开展工作。

两个步骤。

3.1 boot整合Junit

test里的这个类就是整合Junit的类。

随便建一个boot工程,Junit是自带的。pom里这个是自动导入的。

1.建一个dao类,里面有方法,测试里面的方法。

2.在test里的那个类里测试。
@SpringBootTest
这个注解定义了这个类是测试类。注入对象,就可以用这个对象的方法。

3.1.1 纯手工做一个boot工程时

3.1.2 测试类的注意事项

如果当前的测试类,在引导类所在的包及其子包下。不需要修改什么,这个测试类就可以启动。

那如果当前的测试类,不在引导类所在的包及其子包下。需要在注解后加一句话。
写上引导类的类名。


3.2 boot整合mybatis


3.2.1 简单整合案例

新建一个项目,SQL那里选上Mybatis Framework。和MySQL Driver

pom文件里发生了一些变化。

配上datasource。

mapper层写好数据库查询方法。

然后在测试类里运行。
3.2.2 整合myBatis过程可能遇到的:错误一


配时区。
解决方法1:url后面加上。

3.2.3 整合myBatis过程可能遇到的:错误二
driver废弃,建议换新的driver。


这里修改成这样即可。

3.3 boot整合mybatis plus


3.3.1正常建立mybatis plus工程
正常建立boot项目,spring里没有引进mybatis plus。只导入mysQL的驱动。

然后去mvn里查,mybatis plus。

找到要用的版本定义的坐标。导入pom。

3.3.2 用aliyun创建。


3.3.3 小案例
在mapper的接口里。继承BaseMapper,把要操作的对象的泛型传进去。

里面有很多写好了的方法。国人开发好的技术。

但后面会遇到一个问题与数据库的表设定有关系。
也就是它自己默认搜的表,没和我们数据库的表对应上。
实体类的名和数据库表名不一样。

方法一:
我们可以在domain里的实体类上加一个注解。@TableName("数据库里的表名。")
方法二:在yml文件里,加上前缀或后缀(与数据库里表的不同)。

3.4 整合Druid 数据源
数据源是一种用来提高数据库连接性能的常规手段,数据源会负责维持一个数据库连接池,当程序创建数据源实例时,系统会一次性地创建多个数据库连接,并把这些数据库连接保存在连接池中。当程序需要进行数据库访问时,无须重新获得数据库连接,而是从连接池中取出一个空闲的数据库连接,当程序使用数据库连接访问结束后,无须关闭数据库连接,而是将数据库连接归还给连接池即可。通过这种方式,就可比避免频繁地获取数据库连接,关闭数据库连接所导致的性能下降。
数据源是给mybatis用的。

方式一:
导入druid坐标。

在yml文件里配上

方式二:没导druid的starter。

4. 基于SpringBoot的SSMP整合案例。

4.1 项目搭环境
建一个项目,导入MP得坐标。

4.2 实体类和数据库表
自己建一个数据库表

用lombok,必须得按lombok插件
boot里收录了,lombok得坐标。

实体类加@data注解,除了构造方法。例如tostring,get set,hashcode。
都写好了。

4.3 数据层开发、



4.3.1 先导入pom,然后修改配置。
application.yml
server:
port: 80
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssmp?serverTimezone=UTC
username: root
password: a839846976
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
4.3.2 dao接口(跟mapper一样,叫法不一样)
接口dao
package com.book.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.book.domain.Book;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BookDao extends BaseMapper<Book> {
}

4.3.3 增加对象会出现的问题(insert)
加数据的时候, 参数类型对应失败。
这里id,没有像数据库里那样自动生成。
Exception: nested exception is org.apache.ibatis.reflection.ReflectionException:
Could not set property 'id' of 'class com.book.domain.Book' with value '1492005888272625666' Cause: java.lang.IllegalArgumentException: argument type mismatch。


4.3.4 MP的运行日志


这里就选标准的形式。再运行就得到整个运行过程。

4.3.5 分页

在方法里写好分页的方法

如果不用拦截器,结果是,SQL语句里没有limit语句。
这个limit限制,必须得自己配置。

写一个配置类,用拦截器的方法,来加上limit语句。。
Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。
@configuration,标记为配置类,这样可以读取里面的配置信息。
然后@bean注入MP的拦截器。创建MP的拦截器,添加内部拦截器。
添加一个分页的拦截器,PagenationInnerInterceptor。
return出来就行。
就能实现limit分页的功能了。


结果,getRecords,就是得到当前第二页的所有数据。
getTotal就是获取所有数据的条数。
getPages,就是获得这些数据总共分了多少页。

分页查询的代码:
MGConfig.java
package com.book.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor (){
MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
bookService里
IPage<Book> getPage(int currentPage,int pageSize);
bookServiceimpl里
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
4.3.6 按条件查询

里面封装了一个查询对象。可以通过修改这个对象的属性,更改条件。
就比如 like属性。
定义一个查询类型。
用like方法,匹配查询,含有name属性含有,spring,的book实体。
类似这句SQL。


结果,看日志里。

这个方法的改进方法,这样写属性的时候,name属性就不会自己写错了。

这里给name当成字符串了。

解决方法:里面判断一下。是不是null

4.3.7 按条件的分页查询
给前面整的按条件查询的条件代入。

4.4 业务层的开发
MP里的方法,返回值都为int类型,影响几行。


在test建立的service包下测试。
在业务层不像数据库层那样能看详细日志。
调试的时候还是打印来看。
bookService 接口
package com.book.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.book.domain.Book;
import java.util.List;
public interface BookService {
Boolean save(Book book);
Boolean update(Book book);
Boolean delete(Integer id);
Book getById(Integer id);
List<Book> getAll();
IPage<Book> getPage(int currentPage,int pageSize);
}
bookServiceimpl
package com.book.Service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.book.Service.BookService;
import com.book.dao.BookDao;
import com.book.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author Just Ching
* @create 2022-02-11 18:39
*/
@Service
public class BookServiceimpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public Boolean save(Book book) {
return bookDao.insert(book) >0 ;
}
@Override
public Boolean update(Book book) {
return bookDao.updateById(book)>0;
}
@Override
public Boolean delete(Integer id) {
return bookDao.deleteById(id)>0;
}
@Override
public Book getById(Integer id) {
return bookDao.selectById(id);
}
@Override
public List<Book> getAll() {
return bookDao.selectList(null);
}
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
/*或者bookDao.selectPage(page,null);
return page,都是一个东西。都返回一个page*/
}
}
4.4.1 业务层的快速开发
mybatis甚至帮我做了业务层的通用接口和它的实现类。........
但是很蠢,都是写死的方法。

这个类继承IService

然后在Service实现类里,用写好的方法。继承ServiceImpl<dao,对象类型>。

人家没写的方法你就自己写一下。
或者自己重写。
4.5 表现层开发、
对象数据,用@requestBody,接收传过来的JSON数据

BookController.java
package com.book.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.book.Service.BookService;
import com.book.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author Just Ching
* @create 2022-02-11 22:48
*/
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@GetMapping
public List<Book> getAll(){
return bookService.getAll();
}
@PostMapping
public Boolean save(@RequestBody Book book)
{
return bookService.save(book);
}
@PutMapping
public Boolean update(@RequestBody Book book)
{
return bookService.update(book);
}
@DeleteMapping("{id}")
public Boolean delete(@PathVariable Integer id){
return bookService.delete(id);
}
@GetMapping("{id}")
public Book getById(@PathVariable Integer id){
return bookService.getById(id);
}
/*http://localhost/books/1/10*/
@GetMapping("{currentPage}/{pageSize}")
public IPage<Book> getPage(@PathVariable int currentPage,int pageSize){
return bookService.getPage(currentPage, pageSize);
}
}
4.5.1 表现层消息一致性处理
主要是让前端看到操作成功没有
只要前后端协议好了就行。

前后端数据协议。


R.java
package com.book.controller.utils;
import lombok.Data;
@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;
}
}
BookController2.java
package com.book.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.book.Service.BookService;
import com.book.controller.utils.R;
import com.book.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController2 {
@Autowired
private BookService bookService;
@GetMapping
public R getAll(){
return new R(true,bookService.getAll());
}
@PostMapping
public R save(@RequestBody Book book)
{
return new R(bookService.save(book));
}
@PutMapping
public R update(@RequestBody Book book)
{
return new R(bookService.update(book));
}
@DeleteMapping("{id}")
public R delete(@PathVariable Integer id){
return new R(bookService.delete(id));
}
@GetMapping("{id}")
public R getById(@PathVariable Integer id)
{
return new R(true,bookService.getById(id));
/*这里直接flag是true*/
}
/*http://localhost/books/1/10*/
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,int pageSize){
return new R(true,bookService.getPage(currentPage,pageSize));
}
}
4.5 前端的开发

不然会有idea的小bug。


4.5.1 刷页面的时候数据显示到页面。


然后在控制台,看显示的信息。


这里发的是get请求,如果是post请求。.post

我们下一步要让res.data展示出来。要展示的表的数据来源“dataList”。
我们只需要让dataList里面有数据,就能展示出来。
EL UI,的双向数据绑定。



res的data里,有flag和data。所以要再写一个。
4.5.2 新增数据
![]()
触发handleCreate()函数。 然后把新增的界面展示出来

变为true就行。

重启项目,就能弹出。这是个弹出页面。点击之后,
在handle方法里,把弹出属性设置为true,就弹出了。

然后写完数据,点确定,我们要把数据发到后台。它关联一个handleAdd(),方法。

model是formData。
我们把这个formData,给post过去

flag为true了。

添加成功或失败的功前端源码:
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
//判断当前操作是否成功
if(res.data.flag){
/*1.成功关闭弹层*/
this.dialogFormVisible= false;
this.$message.success("添加成功");
}else {
this.$message.error("添加失败");
}
/*不管成功失败,都要查询一遍结果*/
}).finally(()=>{
this.getAll();
});
},
但随之而来的小问题,随着我们新建一个图书,里面的值还在。所以我们需要,每次弹层的时候,
清空里面的数据。

给里面的formData清空。

取消按钮的功能
关闭弹层

4.5.3 删除功能
ELMENT UI里面把行封装成对象。row指的是这一行的数据。

我们随便点一个删除,把操作打成consloe.log(row),控制台里看row的信息。

,如果手抖删错了也不行,得在删除前做一个提醒。

然后点确定才进行删除。写一下确定按钮对应的功能。catch操作的是取消按钮,没有catch就是执行确定的按钮。

我们之前写好的删除功能如下:
把它和删除前的确认结合即可。
handleDelete(row) {
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.flag){
/*1.成功关闭弹层*/
this.$message.success("删除成功");
this.dialogFormVisible= false;
}else {
this.$message.error("删除失败");
}
}).finally(()=>{
this.getAll();
});
},
结合后的代码:
// 删除
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("取消操作");
});
},
4.5.4 修改功能(编辑功能)
点开修改功能,类似添加页面,里面有回显的数据。然后修改这些数据。

先弹出编辑窗口
//弹出编辑窗口
handleUpdate(row) {
/*后端 update功能是get方法。*/
axios.get("/books/"+row.id).then((res)=> {
if(res.data.flag && res.data.data != null)
{
//编辑用的弹出窗口。
this.dialogFormVisible4Edit = true;
this.formData = res.data.data;
}else {
this.$message.error("数据同步失败,自动刷新")
}
}).finally(()=>{
this.getAll();
});
},
然后在编辑窗口里修改。看看弹层里对应的方法,写那个方法。

这个handleEdit功能给表里的数据传进去。跟添加功能一样,给弹出的那个页面的数据,传进数据库。不过这个弹层的

点取消去cancel方法。

4.5.5 异常消息处理原理
抛一个数据库IO异常。

正常返回给前台,不是true就是false,你这里抛出的异常给前端,别人不好处理。

我们要保证的,除了异常,也要给前端他们读的懂的数据。
SpringMVC里有异常处理器,在表现层里处理就好,因为异常会一层一层往上抛出去。
这里拦截异常,然后把它处理成R对象传给前台。
这个注解里可以放,你想拦截的异常类型。
这里用@RestControllerAdvice,是因为要用response。可以点开注解进去看看。



我们在controller抛出一个异常,检查一下。
@PostMapping
public R save(@RequestBody Book book) throws IOException {
if(book.getName().equals("123")) throw new IOException();
return new R(bookService.save(book));
}
在前台接收这个msg。


4.5.6 异常处理最终版本
R里整个构造器。



4.5.7 分页功能(改进后的getALL方法,分页查询所有)
页面上的布局 LayOut


vue里定义的三个初始数据。前端定义的分页的值。

我们需要给从前端,把值传给后台让它做一个分页处理。然后把值返回给前台。
这里@PathVariable。注解,一个参数后面要跟一个注解。这里必须两个

然后我们点击要跳到的页面的数字。

这里会返回一个currentPage。点下2,currentPage=2。我们在红线的方法里处理。

有两个BUG,
1是id的问题,每次都是从1开始。
解决:这里绑定id即可。

2.是如果当前页数据删完了,想跳到第三页,第三页却已经不存在。

解决:如果当前的页码值,大于总页码值数,使用最大页码值作为当前页码值。


4.5.8 条件查询

这三条数据没有绑定。而且点了这个查询按钮,跳到getALL方法,说明条件查询做在getAll方法里。
定义这一下这三个数据模型。

完成绑定。

然后我们接收查询里传来的信息

通过param参数传到后端。
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.total=res.data.data.total;
this.dataList=res.data.data.records;
});
},
在controller层里用book对象接收。

输出可看到。
![]()
把book放进方法里,接口里没有这个方法。点击refactor,添加这个方法。

service层改成这样,然后去实现类里改。

实现类里重写的方法。实现了模糊查询。
@Override
public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
LambdaQueryWrapper <Book> lqw =new LambdaQueryWrapper<Book>();
lqw.like(Strings.isEmpty(book.getType()),Book::getType,book.getType());
lqw.like(Strings.isEmpty(book.getName()),Book::getName,book.getName());
lqw.like(Strings.isEmpty(book.getDescription()),Book::getDescription,book.getDescription());
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,lqw);
/*或者bookDao.selectPage(page,null);
return page,都是一个东西。都返回一个page*/
}
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
/*或者bookDao.selectPage(page,null);
return page,都是一个东西。都返回一个page*/
}
4.6 ssmp案例 总结与后面展望。













