Spring Boot基础篇

Restful风格

REST开发

REST,表现形式状态切换

优点:

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 书写简化

@RestController:将当前控制器类设置为RESTful风格,等同于@Controller与@ResponseBody两个注解组合功能

SpringMVC 常用注解 - 云+社区 - 腾讯云 (tencent.com)

将请求参数放入请求路径中,再由注解**@PathVariable**(“对应请求参数的名称”) 从而获得相对应的值,有多个参数就多个使用

RESTful API 一种流行的 API 设计风格

Restful原则:行为操作(资源的访问形式)

  • 增:post请求 @PostMapping
  • 删:delete请求 @DeleteMapping
  • 改:put请求 @PutMapping
  • 查:get请求 @GetMapping

一般来说请求路径不要出现动词

分页,排序等操作,不需要使用斜杠传参,一般传的参数不是数据库表的字段,可以不采用斜杠

@RequestBody @RequestParam @PathVariable

  • 区别
    • @RequestParam 用于接收url地址传参或者表单传参
    • @RequestBody用于接收json数据
    • @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
  • 应用
    • 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody 应用较广
    • 如果发送非json格式数据,选用@RequestParam接收请求参数
    • 采用RESTful进行开发,当参数较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值

springboot应用

springboot是简化Spring应用的初始搭建和开发过程 springboot简化了依赖的配置

Spring Boot Web 开发@Controller @RestController 使用教程 - fishpro - 博客园 (cnblogs.com)

将spring的配置文件和spring-mvc配置简化了

springboot的配置文件中 pom文件的继承父类的文件,父类的文件里规定了dependency坐标依赖的最好版本 自动配置

starter和parent

  • starter
    • springboot中常见的项目名称,定义了当前项目使用的所有依赖坐标,以达到减少依赖配置的目的
  • parent
    • 所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的
    • spring-boot-starter-parent各版本间存在着诸多坐标版本不同
1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
  • 实际开发 GAV(G:groupId A:artifactId V:version)
    • 使用任意坐标时,仅写GAV中的G和A,V由SpringBoot提供,除非SpringBoot为提供对应版本V
    • 如果发生坐标错误,则需要手动指定Version(但要小心版本冲突)

引导类

SpringBoot的引导类是Boot工厂的执行入口,通过mian方法就可以启动项目

加载spring容器 默认扫描对应包及其子包的内容 实例bean 放入spring容器

层次

1
2
3
4
5
6
7
8
@SpringBootApplication
public class SpringbootQuickApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootQuickApplication.class, args);
}

}

内嵌tomcat(springboot支持切换web服务器)

在pom文件里引入的spring-boot-starter-web里自动加载tomcat服务器

tomcat的本质是用java语言编写的执行过程 现在SpringBoot将tomcat的执行过程封装成一个对象放入spring容器里,需要使用web程序直接引用tomcat对象,从而在tomcat上执行,所以SpringBoot不用配置tomcat服务器也可以使用tomcat

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.6.7</version>
<scope>compile</scope>
</dependency>

变更内嵌服务器:是将现有的服务器去除,添加全新的服务器

SpringBoot可以支持切换内置服务器有三个

  • tomcat(默认) apache出品,应用面广,负载若干较重的组件
  • jetty 更轻量级,可扩展性更高,负载性能远不及tomcat
  • undertow 负载性能勉强跑赢tomcat

配置文件

复制工程(快速新建模块)

  • 原则
    • 保留工程基础结构
    • 抹掉原始工程痕迹

在工作空间里复制对应工程,兵修改过程名称

删除与Idea相关的配置文件,仅保留src目录和pom文件

修改pom文件中的artifactId与新工程/模块名相同

保留备份后期使用

springboot配置文件(三种):

  • application.properties (主导)使用key-value的格式

server.port=80

  • application.yml(主流)

server:

​ port: 80

  • application.yaml(配置同上)

不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留

yml配置文件里属性

使用@Value注解读取单个属性

1
2
@Value("${country}")
private String country1;

使用@Autowired获得全部属性

1
2
3
//自动装配 yml配置文件里的全部属性
@Autowired
private Environment env;
1
env.getProperty("user.name")

加载实体的get set方法使用lombok里注解**@Data自动加载,需要在pom文件加入lombok**的坐标

将一个类转换为bean用spring注解:**@Component**将其转换为bean并放入spring容器里

获得yml指定区域数据:**@ConfigurationProperties(prefix=”字段名”)**

springboot整合junit

测试类需要在启动类的包或子包中,可以省略启动类的设置,也就是省略calsses的设定

否则需要使用**@SpringBootTest(calsses = 启动类名)**注解来查找

SpringBoot整合junit相当于spring整合junit

spring整合junit是 使用@RunWith()+ContextConfiguration(calsses = 启动类名)

**而springboot是使用@SpringBootTest(calsses = 启动类名)**注解

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest(classes = Springboot03JunitApplication.class)
class Springboot03JunitApplicationTests {

@Autowired
private BookDao bookDao;

@Test
void contextLoads() {
bookDao.save();
}

}

springboot整合mybatis

在yml配置文件里添加配置数据库的相对应信息

1
2
3
4
5
6
7
# 配置相关信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test
username: root
password: nian0209

创建实体类domain和dao实现操作数据库的方法 注意:实体类名应该和数据库的字段名一致

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootTest
class Springboot04MybatisApplicationTests {

//这里虽是接口的注入但实际注入的对象是实现类的对象
@Autowired
private BookDao bookDao;

@Test
void contextLoads() {
System.out.println(bookDao.getById(2));
}

}

至于在controller层上使用的service的接口而不是实现类

因为通过**@Autowired的对象是通过接口的方式会使用jdk的动态代理**,jdk的动态代理针对接口产生代理,动态的产生实现类的对象,在注入到spring容器里也是实现类的对象 这样使用jdk代理的方式动态的生成接口的实现类,还可以实现对实现类的增强从而做到增强类

一个接口里有多个实现类

Spring中如Service有多个实现类,它怎么知道该注入哪个ServiceImpl类?_Java知音_的博客-CSDN博客

@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常

@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用

@Autowired

@Qualifier(“personDaoBean”) 存在多个实例配合使用

在service层使用@Service(名称)来将实现方法带名称的注入到spring容器里

使用@Autowired自动注入,

**@Qualifier(“beanId”)**:beanId是指对应实现类的类名称且字母开头小写

  • 方法一: Controller中注入service的时候使用**@Autowired**自动注入,@Qualifier("beanId")来指定注入哪一个。
  • 方法二: Controller中注入service的时候使用@Resource(type = 类名.class)来指定注入哪一个。
  • 方法三:
    • 每个service的impl都可以指定名称(使用@Service(“名称”)
    • Controller中注入service的时候使用名称来指定注入哪一个(使用@Resource(name="名称")

@Autowired注解的意思就是:

当Spring发现@Autowired注解时,将自动在代码上下文中找到和其匹配(默认是类型匹配)的Bean,并自动注入到相应的地方去。

@Resource的作用相当于@Autowired

@Autowired和@Resource两个注解的区别:

1.@Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了。

2.@Autowired默认按照byType(方法类型)方式进行bean匹配,@Resource默认按照byName(方法名称)方式进行bean匹配。

3.@Autowired默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false)

springboot整合MyBatis-Plus

  • MyBatis-plus与MyBatis区别
    • 导入坐标不同
    • 数据层实现简化

手动导入MP坐标

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>

springboot整合Druid

导入Druid对应的starter

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>

在yml配置文件中配置druid数据源

1
2
3
4
5
6
7
8
# Druid整合
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test
username: root
password: nian0209

SSMP整合案例

基于springboot

  • 案列方案分析
    • 实体类开发—使用lombok快速制作实体类
    • Dao开发—整合MyBatisPlus,制作数据层测试类
    • Service开发—基于MyBatisPlus进行增量开发,制作业务层测试类
    • Controller开发—基于Restful开发,使用PostMan进行接口测试
    • Controller开发—前后端开发协议制作(前后端数据交换形式)
    • 页面开发—基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
      • 列表,新增,修改,删除,分页,查询
    • 项目异常处理
    • 按条件查询—页面功能调整,Controller修正功能,Service修正功能

准备步骤

勾选springweb 和 mysql对应坐标

修改配置文件为yml格式并设置端口并设置自动增长的属性 开启日志

1
2
3
4
5
6
7
mybatis-plus:
global-config:
db-config:
# 设置id自增
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

数据库

分页

使用MP快速的查看分页的数据,但是需要指定spring拦截器 在使用语句前也需要传入分页对应的参数—-(第几页,几条数据)

1
2
3
4
IPage page = new Page(1,3);
bookDao.selectPage(page,null);
//打印当前页内数据
System.out.println(page.getRecords());
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MPConfig {

//使用拦截器创建分页
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//定义MP拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加具体的拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}

条件查询

使用QueryWrapper则需要自己去写对应匹配查询的字段,使用LambdaQueryWrapper则直接get就行了

like方法里可以添加判断条件:不为空的时候才执行条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//按条件查询
@Test
void testBy() {

QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
queryWrapper.like(true,"name","学");
bookDao.selectList(queryWrapper);

}
//按条件查询
@Test
void testBy2() {

String name = null;
LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(name!=null,Book::getName,name);
bookDao.selectList(lambdaQueryWrapper);

}

业务层-快速开发

实现Service接口和实现类

使用MyBatisPlus提供业务层通用接口(IService)与业务层通用实现类(ServiceImpl<M,T>)

在通用类基础上做功能重载或追加

注意重载时不要覆盖原始操作,避免原始提供的功能丢失—–可以在方法名上添加@Override查看有无重名

1
2
public interface IBookService extends IService<Book> {
}
1
2
3
@Service
public class IBookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
}

表现层展示

接收参数:

实体数据:@RequestBody

路径变量:@PathVariable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
//查
@GetMapping
public List<Book> getAll(){
return bookService.list();
}
//增
@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 int id){
return bookService.delete(id);
}
//查
@GetMapping("{id}")
public Book getById(@PathVariable int id){
return bookService.getById(id);
}
}

表现层信息一致性处理

设计表现层返回结果的模型类,用于后端和前端进行数据格式统一,也称为前后端数据协议

flag:代表查询是否成功

data:存放数据

同意表现层数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Data
public class R {
private boolean flag;
private Object data;
R(){

}
public R(Boolean flag){
this.flag =flag;
}

public R(Boolean flag,Object data){
this.data = data;
this.flag = flag;
}

public R(Boolean flag,String msg){
this.flag = flag;
this.msg = msg;
}

public R(String msg){
this.flag = false;
this.msg = msg;
}
}

前后端 页面

前后端协议联调

  • 前后端分离结构设计在页面归属前端服务器
  • 单体工程页面在resoures目录下的static目录中(建议执行clean)

发送异步请求,调用后端接口

axios.get(“/books”).then((res)=>{

​ 数据连接

});

异常处理

自定义springmvc的异常处理类来处理异常 在类上添加@RestControllerAdvice定义restful风格异常处理类且返回的数据给前端也需要和

前后端数据协议一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//作为springmvc的异常处理器
//@ControllerAdvice
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有的异常信息
@ExceptionHandler(Exception.class)
public R doException(Exception ex){
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new R("服务器故障,请稍后再试!");
}
}

条件查询

分页部分的条件查询是根据v-model动态模型,在分页的基础上动态的添加查询数据,一起传到后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//分页查询
getAll() {
//组织参数,拼接url请求地址
// console.log(this.pagination.type);
param = "?type="+this.pagination.type;
param +="&name="+this.pagination.name;
param +="&description="+this.pagination.description;
// console.log(param);

//发送异步请求
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res)=>{
this.pagination.pageSize = res.data.data.size;
this.pagination.currentPage = res.data.data.current;
this.pagination.total = res.data.data.total;

this.dataList = res.data.data.records;
});
},

service层判断条件查询有无值

1
2
3
4
5
6
7
8
9
10
@Override
public IPage<Book> getPage(int currentPage, int pageSize, 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());
IPage page = new Page(currentPage,pageSize);
bookDao.selectPage(page,lqw);
return page;
}

elementUI

分页插件

分页bug:一页中只有一个数据的时候。删除这个数据后当前页不会有信息展示,例如:原来有三页,删除了一页中只有一个数据的时候,系统还是在查删除完了那页,所以不会显示数据

解决方法:一般是在后台判断当前页是不是比总页数大,为true则返回最大页数数据 ———–但这也还是有bug

较好的解决方法是直接返回第一页

根本:根据需求修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--分页组件-->
<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>
1
2
3
4
5
6
7
8
9
10
11
    @GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book book){
// System.out.println("参数==>"+book);

IPage<Book> page = bookService.getPage(currentPage, pageSize,book);
//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
if( currentPage > page.getPages()){
page = bookService.getPage((int)page.getPages(), pageSize,book);
}
return new R(true, page);
}

注意事项

在对应的实体类中,如果数据库和实体类的字段是驼峰命名MP会将其转化为下划线形式,如果需要对应有三种办法

  • 将数据库表字段改为下划线形式

  • 在实体类字段上添加@tableFiled(“字段名”)

  • 在yml配置文件中的mybatis-pus添加

  • mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        map-underscore-to-camel-case: false #关闭驼峰映射
    

实体类有主键的需要在实体类主键字段头添加@TableId(type = IdType.AUTO(自增长类型))