Spring Boot是一个开源的Java框架,旨在简化Spring应用程序的开发过程。
Spring Boot的主要特点包括:
- 自动配置:根据项目的依赖自动配置Spring应用程序,减少了手动配置的复杂性。
- 独立运行:可以创建独立的Java应用程序,内嵌Tomcat、Jetty等服务器,无需外部服务器支持。
- 生产就绪:提供了多种生产环境的特性,如监控、健康检查和外部配置等。
- 简化的构建:通过Spring Initializr等工具,快速生成项目结构和依赖配置。
- 广泛的社区支持:拥有活跃的社区和丰富的文档,便于开发者获取帮助和资源。
Spring Boot适用于微服务架构、RESTful API开发以及传统的Web应用程序开发。
1.快速入门
1.创建工程
创建springboot工程 , 勾选web开发相关依赖(spring web)
create 基本项目后 , 先看下pom.xml文件
<parent>表示继承父工程 , 这个标签的内容表示的是继承的父工程的坐标
其他的标签大致为 : 本工程的坐标 , 本工程的依赖(默认两个 , springboot相关)
再看下main文件夹下面的文件夹
一个是java , 一个是resource .
其中 java 最下面放的有一个启动类 SpringbootFirstApplication (自动创建好了)
resource 下面放了三个项目 : static , templates , 还有一个properties配置文件
2.创建处理类
启动类同目录下创建一个处理层 : controller(文件夹) , 然后下面新建一个Controller 处理类 , 类名我们这里取RequestController
注意先将java目录标记为 源代码根目录 , 然后要 reload一下 maven
2.HTTP协议
hyper text transfer protocol 超文本传输协议 , 规定了浏览器和服务器之间数据传输的规则
三大特点 :
- 基于TCP协议 : 面向连接 , 安全
- 基于请求-响应模型
- 无状态的协议 , 对于事务处理没有记忆能力
HTTP-请求数据格式 : 请求行/头/体
两种基本请求方式 :
- get , 请求参数在请求行中, 没有请求体
- post , 请求参数在请求体中 , post请求大小是没有限制的
HTTP-响应格式 : 响应行/头/体
3.Tomcat 服务器
下面介绍一下基本安装和使用
官网下载好zip后解压 , 运行bin目录下的startup.bat 文件
会弹出控制台 , 然后有中文乱码的情况 , 先关闭 (直接点x 或者 运行bin目录下面的shutdown.bat文件 或者 ctrl+c ) , 只要把 ( conf/logging.properties 文件) 的编码格式 从utf-8 改成 GBK就行
java.util.logging.ConsoleHandler.encoding = GBK
然后可以修改tomcat的默认端口 , 找到 conf/server.xml文件
<Connector port="9000" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
这里把 port 改成 9000 , 那么默认端口就是9000
那么该怎么基于tomcat 部署项目呢?
将项目放到webapps 目录下面即可
4.请求
使用postman发送请求
参数携带 :
1.简单参数
在springboot项目中 , 先在controller类中配置请求处理类 和方法 , 这里我们如下声明 :
package com.kitten.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
测试请求方式接收
*/
@RestController
public class RequestController {
@RequestMapping("/simpleParam")
public String simpleParam(HttpServletRequest request){
//获取请求参数
String name = request.getParameter("name");
String ageStr = request.getParameter("age");
int age = Integer.parseInt(ageStr);
System.out.println("name:"+name + "age:"+age);
return "ok";
}
}
然后在postman中 基于上面的 测试接口的路径发送一条get请求
http://localhost:8080/simpleParam?name=kitten&age=15
其中 ? 表示在路径后面携带参数 , & 符号连接多个携带的参数
发送请求后 , 后端服务器就会响应这条请求 , 向前端发送一个 "ok" , 然后服务器这里打印 postman 发送过来的参数 name 和 age
上面这个服务器端的小例子是 基于 HttpServletRequest 实现的一个接口 , 它其实是比较繁琐的而且还需要我们手动进行一个类型转换
下面我们可以使用基于springboot 的方式来优化一下 , 在上面的类中我们新增方法
//2.使用springboot方式 , 请求处理类中添加下面的方法
@RequestMapping("/simpleParam_spring")
public String simpleParam_spring(String name,Integer age){
System.out.println("name:"+name + " || age:"+age);
return "ok";
}
下面尝试发起 post请求
在postman中 , 我们将请求方式改为 post , 因为 post 请求的请求参数是在请求体中的 , 所以我们在下面的body选项中 , 选择x-www-from-urlencoded 这个from表单 , 然后填写对应的key-value 参数就行
如果你填写的请求参数的 key(参数名) 和接口定义的 要求参数不一样 , 那么服务器也不会报错 , 但是会返回接收到 null
当然我们可以使用@RequestParam这个注解完成映射
@RequestMapping("/simpleParam_spring")
public String simpleParam_spring(@RequestParam(name="name")String username, Integer age){
System.out.println("name:"+username + " || age:"+age);
return "ok";
}
上面这个表示从前端接收的请求参数的"name"要赋值给方法的参数"username" , 但是要注意这个注解 有一个 required 属性,默认值是true , 表示该参数必须传递 , 如果不传递将报错 (返回一个400)
public String simpleParam_spring(@RequestParam(name="name",required=false)String username, Integer age){
System.out.println("name:"+name + " || age:"+age);
return "ok";
}
2.实体参数
当参数很多时,我们通常会将参数封装到一个对象中 , 那么这个参数我们叫它实体参数 , 建立实体参数步骤如下 :
建立 controller 包同级包 pojo (plain old java object) 简单java对象 , 我们在这个包下面会建立一系列的类
例如这里我们先创建一个user类 , 它封装了 两个私有属性 name , age ,我们设置一下getter和setter , 然后在RequestController下这样写一个方法 :
//3.实体参数 , 接收一个自定义类(实体对象)
@RequestMapping("/pojoParam_spring")
public String pojoParam(User user){
System.out.println(user);
return "ok";
}
然后我们仍然可以发送一条请求 :
http://localhost:8080/pojoParam_spring?name=kitten&age=14
那么这个请求携带的参数就会被服务器封装到 一个User对象中
复杂实体对象
如果我们接收的是一一个复杂实体对象 , 比如 user 中有一个属性birthday , birthday也是一个类 , 这个类中有 year , month ,day这三个属性 , 那么我们该如何传递 ?
http://localhost:8080/pojoParam_spring?name=kitten&age=14&birthday.year=2005&birthday.month=4&birthday.day=18
可以通过 类名.属性值 的方式来传递 , 如上
3.数组集合参数
先说一下场景 , 网页中的表单通常会有 复选框 , 可以选中 其中多个选项 作为数据参数 , 那么这时候我们们就可以使用数组集合来 维护选中的这一系列参数 , 比如兴趣爱好我们可以选择 打球,玩游戏,看直播等等 , 这时候参数传递方式可以写成如下形式 :
http://localhost:8080/arrayParam_spring?hobby=ball&hobby=game&hobby=live
就是对同一个参数不同值我们使用&连接
那么我们后端接收这个请求的 接收方式 有下面两种 :
-
数组
@RequestMapping("/arrayParam_spring") public String arrayParam(String[] hobby){ System.out.println("hobby"+ Arrays.toString(hobby)); return "ok"; } -
集合
@RequestMapping("/listParam_spring") public String arrayParam_list(@RequestParam Listhobby){ System.out.println("hobby:"+hobby); return "ok"; }
注意后端使用集合接收数组参数时 , 要使用@RequestParam 这个注解 , 这样传递过来的参数就会被封装到集合中
4.日期参数
springboot中我们使用@DateTimeFormat 注解完成日期参数格式转换 , 然后在里面指定日期参数的格式
@RequestMapping("/dateParam_spring")
public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime dateTime){
System.out.println("dateTime:"+dateTime);
return "ok";
}
请求:
http://localhost:8080/dateParam_spring?dateTime=2022-12-12 10:42:27
5.json参数
这个是使用较多的, 然后json 格式的数据(参数) 是要放在请求体中的 , 所以通常使用 post 请求
postman中 , 我们在 请求体(body) 的 raw 选项中定义数据 , 然后在后面定义 JSON , 示例:
{
"name":"kitten",
"age":16,
"birthday":{
"year":2005,
"month":4,
"day":18
}
}
// 接口中我们使用 RequestBody 来定义
@RequestMapping("/jsonParam_spring")
public String jsonParam(@RequestBody User user){
System.out.println("user:"+user);
return "ok";
}
6.路径参数
可以理解为动态路由参数
对于如下请求 :
http://localhost:8080/path_spring/8
--
http://localhost:8080/path_spring/8/kitten
//路径参数
@RequestMapping("/path_spring/{id}")
public String pathParam(@PathVariable Integer id){
System.out.println(id);
return "ok";
}
//传递多个路径参数
@RequestMapping("/path_spring/{id}/{name}")
public String pathParam(@PathVariable Integer id, @PathVariable String name){
System.out.println(id + "||" + name);
return "ok";
}
5.响应
上面接口函数的返回值会返回给客户端 , 这个依赖了一个注解 @ResponseBody , ta的作用是将方法返回值直接响应 , 如果返回值类型是实体对象/集合 , 将会转换成json 格式响应给客户端
另外 : @RestController = @Controller + @ResponseBody
在项目接口中 , 不同的响应结果会造成我们的后端代码 难以维护 , 所以我们一般都会将响应结果封装为一个类 Result , 返回这个类的一些信息就行 , 下面是示例 :
创建实体层 pojo , 里面创建实体类 Result
@RequestMapping("/result_config")
public Result result_config(){
System.out.println("hello ,world");
return new Result(1,"success","hello,world");
// return Result.success("hello,world")
}
//Result类
package com.kitten.pojo;
public class Result {
private Integer code; //1成功 , 0失败
private String msg;
private Object data;
//全+无参构造器,get/set+toString方法
...
}
发送请求 :
http://localhost:8080/result_config
发送请求后,就能返回一个 Result对象 , ta会被转换为JSON格式
6.分层解耦
当我们接口要实现的功能比较复杂时 , 上面这种直接在接口方法中实现功能的代码复用性差 , 而且项目也会变得难以维护 , 这时候我们就依赖 分层解耦
1.三层架构
一个接口实现的任务我们通常分为三层 :
- controller : 控制层 , 接收前端发送的请求, 并进行处理
- service : 业务逻辑层 , 处理具体的业务逻辑
- dao : 数据访问层(Data Access Object) (持久层) , 负责数据访问操作 , 包括数据的增删改查
具体实现还请自行了解
2.IOC & DI
引入
先说下分层解耦的概念 : 分层实现解除耦合
内聚 : 软件中各个功能模块内部的功能联系
耦合 : 衡量软件中各个层/模块之间的依赖 , 关联的程度
在一个三次架构的案例中 , controller 中会new 一个service类的成员 , 而当我们要修改 service下的代码/切换使用的service类时 , 就需要改动controller中的代码 , 我们称 controller 和 service 耦合了 , 这时就需要 控制反转 和 依赖注入
- IOC (inversion of Control ) 控制反转 : 对象的创建控制权由程序自身转移到外部 (容器里) , 这种思想称为控制反转
- DI (dependency injection ) 依赖注入 : 容器为应用程序提供运行时 所依赖的资源 , 称为依赖注入
- Bean 对象 : IOC容器创建 , 管理的对象 , 称为 bean
如何分层解耦 ? 简单说下步骤 :
- service 层 及 dao 层的实现类 , 交给 IOC 容器管理 ( 用注解 @Component)
- 为 Controller 及 Service 注入运行时所依赖的对象 ( 用注解 @Autowired )
- 运行测试
details IOC-DI
| 注解 | 说明 | 位置 |
|---|---|---|
| @Component | 声明bean的基础注解 | 不属于下面三类时,用该注解 |
| @Controller | @Component的衍生注解 | 标注在控制器类上 |
| @Service | @Component的衍生注解 | 标注在业务类上 |
| @Repository | @Component的衍生注解 | 标注在数据访问类上 |
bean组件扫描
上面声明的四大注解 , 想要生效,还需要被组件扫描注解@ComponentScan 扫描
虽然这个注解没有被显式配置 , 但是实际上包含在启动类声明注解 @SpringBootApplication 中,默认的扫描范围是启动类所在的包及其子包
details DI-DI
比如说 , 我们创建了两个service类 , A和B , 当这两个类同时加上@Service注解后 , 那么程序运行时就不知道 该将依赖注入哪个service类 , 服务器就会报错 , 下面是解决方案
- @Primary (在A和B类上选择一个直接添加 , 添加的类会作为 依赖注入的对象类)
- @Qualifier (在@Autowired 注解下声明第二个注解 , 然后添加依赖注入类 )
- @Resource (和上面那个相似 , 但是里面的值是名称)
小题 : @Resource 和 @Autowired 的区别
@Autowired 是spring框架提供的注解 , @Resource 是JDK提供的注解 , 而且前者是按照类型注入,后者是按照名称注入
7.Mybatis
一个持久层框架 , 用于简化 JDBC 的开发 , 支持 自定义 SQL、存储过程以及高级映射。
使用方式 :
1.准备 → 创建springboot工程 , 数据库表user , 实体类user
2.引入 mybatis 依赖 , 配置数据库连接信息
3.编写 SQL 语句(注解XML)
连接信息 :
- driver 驱动
- url 数据库连接的url
- 用户名 + 密码
上手 : 新建modules → 勾选 SQL 里的 mybatis framework 和 MySQL driver
1.配置user表, 表中插入一些基本数据
2.创建com.kitten/pojo/user 实体类
public class user{
private Integer id;
private String name;
private short age;
private short gender;
private String phone;
...构造器/ get set 方法 / toString() 方法
}
3.创建com.kitten/mapper/UserMapper接口
package com.kitten.mapper;
import com.kitten.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper //会自动生成该接口的实现类对象(代理对象),并将对象交给 IOC 容器管理
public interface UserMapper {
// 查询全部用户信息
@Select("select * from user")
public List list();
}
resources下的application.properties配置文件中配置数据库信息
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url , 连接的是自己创建的mybatis数据库
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=****
最后我们在test 目录下测试
test/java/com.kitten/...quickStart.java
package com.kitten;
import com.kitten.mapper.UserMapper;
import com.kitten.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest // springboot 整合单元测试的 类
class SpringbootMybatisQuickstartApplicationTests {
@Autowired // DI依赖注入 , 注入IOC容器管理对象
public UserMapper userMapper;
@Test
public void testListUser(){
List<User> list = userMapper.list();
list.forEach(item ->{
System.out.println(item);
});
}
}
// 最后的输出结果就是 你user表中存放的 数据
一个mybatis 小demo就这样完成了
使用配置
在编写mapper的时候 , 如果sql语句写错了 , 但是idea默认是不会报错的 , 只有你运行程序时 , 程序才会报错 . 则对我们开发人员来说非常不友好 , 所以我们要对 mybatis 进行一些配置
右键sql语句 , 点击 show text actions → inject language or reference , 23版idea是自动配置好了
JDBC
java database connectivity , 就是用java语言操作 数据库的一套 API
public void test() throws Exception{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.driver")
//2.获取连接对象
String url = "jdbc:mysql://localhost:3306/mybatis";
String username = "root";
String password = "admin123";
Connection connect = DriverManager.getConnection(url,username,password);
//3. 获取statement
String sql = "select * from user";
Statement statement = connection.createStatement();
Result result = statement.excuteQuery(sql);
// 封装结果数据
List userList = new ArrayList<>();
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
short age = result.getShort("age");
short gender = result.getShort("gender");
String phone = resultSet.getString("phone");
User user = new User(id,name,age,gender,phone);
userList.add(user)
}
statement.close();
connection.close();
userList.stream().forEach(item->{
sout -> item
})
}
连接池
springboot默认的连接池是 hikari 追光者 , 它实现了sun公司提供的 datasource 接口 , 所有的连接池要实现功能都需要实现这个接口
如果我们想使用德鲁伊 (阿里巴巴) 的连接池 , 需要做一些很简单的 配置:
pom.xml 中添加 druid 的配置依赖 , 可以在 druid 的官方文档中查看
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version>
</dependency>
application.properties 中添加数据库连接信息
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库url
spring.datasource.druid.url=jdbc:mysql://localhost:3306/mybatis
#数据库连接用户名
spring.datasource.druid.username=root
#数据库密码
spring.datasource.druid.password=***
lombok 工具包
我们在写实体类的时候 , 代码非常的臃肿. lombok这个工具就是解决这个问题的
只用在实体类上增加一个 @Data , 那么就会自动为实体类中的属性生成 get/set + toString + Equals + hashCode 方法; @NoArgsConstructor 会自动生成 无参构造方法; @AllArgsConstructor 会自动生成 带全部参数的 构造器
mybatis基础操作
这部分单独开了文档….
Mybatis使用综合案例
本案例主要实现的是对emp表中员工的管理和对dept表下部门的管理
前期准备
- 创建项目,勾选web , mybatis, spring web,lmbok,mysql driver 选项
-
配置文件信息 :`resources/application.properties`
spring.application.name=tlias-management # 驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据库连接的url , 连接的是自己创建的mybatis数据库 spring.datasource.url=jdbc:mysql://localhost:3306/tlias # 连接数据库的用户名 spring.datasource.username=root # 连接数据库的密码,填写你自己的密码 spring.datasource.password=**** # mybatis日志 / 驼峰自动转换 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis.configuration.map-underscore-to-camel-case=true -
创建工程文件 :
- `com/kitten`下创建四个包 : mapper , service , pojo , controller
- `pojo`下创建两个实体类Emp和Dept , 对应要管理的表的emp和dept , 实体类的属性可以看着后面的写
- `controller`下创建两个类 , 一个EmpController , 一个DeptController
package com.kitten.controller; import org.springframework.web.bind.annotation.RestController; /** * 员工管理Controller */ @RestController public class EmpController {} // deptcontroller也类似 - `service`层下面建一个包`impl`用来统一存放实现接口的类,还建两个接口`DeptService`和`EmpService`, `impl`下新建两个实现类`DeptServiceImpl`和`EmpServiceImpl`
package com.kitten.service.impl; import com.kitten.service.DeptService; import org.springframework.stereotype.Service; @Service public class DeptServiceImpl implements DeptService {} - `mapper`层下创建两个接口 `deptMapper`和`empMapper`,该层用于访问数据库
- 创建数据库alias , 数据库创建两个表Emp和Dept , 插入数据(自己随便插入,字段名称可以看看后面的)
Restful开发规范
REpresentational State Transfer , 表述性状态转换 , 是一种软件架构风格
com/kitten/pojo下创建统一响应结果Result类
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应信息 描述字符串
private Object data; //返回的数据
//增删改 成功响应
public static Result success(){
return new Result(1,"success",null);
}
//查询 成功响应
public static Result success(Object data){
return new Result(1,"success",data);
}
//失败响应
public static Result error(String msg){
return new Result(0,msg,null);
}
}
注意开发流程 : controller →service →mapper
接口
1.获取部门列表
com.kitten/Controller/DeptController
package com.kitten.controller;
import com.kitten.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* 部门管理Controller
*/
@Slf4j
@RestController
public class DeptController {
// 1.获取部门列表
// @RequestMapping(value = "/depts" , method = RequestMethod.GET)
@GetMapping("/depts")
public Result getDept(){
log.info("查询部门数据");
return Result.success();
}
}
先写一个接口的架子 , 不处理逻辑 , 说一下这个架子中的一些点
- @Slf4j 注解为logback提供的注解,会自动为当前类提供日志log对象
- @RestController包含@ResponseBody注解 , 会将返回对象(
Result)转换为json格式并响应 - 方法上面可以通过
@RequestMapping(value = "/depts" , method = RequestMethod.GET)来指定请求方法 , 一般我们直接用@Get/Delete/…Mapping这个注解来代替 @RequestMapping(…)
接下来实现功能 :
control层
com.kitten/Controller/DeptController
...
public class DeptController {
//注入service处理逻辑层
@Autowired
private DeptServiceImpl deptService;
@GetMapping("/depts")
public Result getDept(){
log.info("查询部门数据");
deptService.getDept();
return Result.success();
}
}
service层 (service接口方法我们这里省略,但是得写,就是写接口的无方法体方法)
com.kitten/Service/Impl/DeptServiceImpl
package com.kitten.service.impl;
import com.kitten.mapper.DeptMapper;
import com.kitten.pojo.Dept;
import com.kitten.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
/**
* 获取部门信息
*/
@Override
public List getDept(){
return deptMapper.getDept();
}
}
Mapper层
com.kitten/mapper/DeptMapper
package com.kitten.mapper;
import com.kitten.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface DeptMapper {
/**
* 查询部门信息
*/
@Select("select * from tlias.dept")
List getDept();
}
启动项目 , 然后postman发送请求 , 就能看到数据库中的dept表中的数据
前后端联调
可以自己写一个前端工程文件 , 用fetch API或 导入axios包 发发请求 , 然后打包放nginx里 , 默认访问测试http://localhost:90 (就可以看见你的网页以及和后端联调测试结果)
2.删除部门
路径 : /depts/{id} 请求方式 : Delete
controller层
com.kitten/Controller/DeptController
...
@DeleteMapping("/depts/{id}")
public Result deleteById(@PathVariable Integer id){
log.info("删除部门操作",id);
deptService.deleteById(id);
return Result.success();
}
service层 (接口这里没展示,记得写)
com.kitten/Service/Impl/DeptServiceImpl
@Service
public class DeptServiceImpl implements DeptService{
...
public void deleteById(id){
deptMapper.deleteById(id);
}
}
com.kitten/Mapper/Impl/DeptMapper
...方法体
/**
* 2.根据id删除部门
*/
@Delete("delete from tlias.dept where id = #{id}")
void deleteDeptById(Integer id);
3.新增部门
路径 : /depts 请求方式 : POST
前端发送json格式的数据:
{
"name": "人事部";
}
在我们后端插入数据到数据库的时候,要补充数据 , 即createTime和updateTime
com.kitten/Controller/DeptController
/**
* 3.新增部门
*/
@PostMapping("/depts") //json格式的数据要封装到对象中,用@RequestBody
public Result addDept(@RequestBody Dept dept){
log.info("新增部门: {}", dept);
deptService.addDept(dept);
return Result.success();
}
com.kitten/Service/Impl/DeptService
该层实现逻辑处理 , 这里就是处理补充数据
/**
* 3.新增部门
*/
public void addDept(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.addDept(dept);
}
com.kitten/Mapper/Impl/DeptMapper
/**
* 新增部门
* @param dept
*/
@Insert("insert into tlias.dept(name, create_time, update_time) values (#{name},#{createTime},#{updateTime})")
void addDept(Dept dept);
简化 : 我们可以看到在 controller下的方法中 , 它们所有的请求路径都是 /depts 开头 , 于是我们就可以将公共的请求路径抽取出来,然后把使用到/depts的方法删去这部分即可
com.kitten/Controller/DeptController
import ...
@Slf4j
@RestController
@RequestMapping("/depts")
public class DeptController{
@GetMapping
public Result queryDept(){...}
@deleteMapping("/{id}")
public Result deleteDeptById(){...}
...
}
最后 , 自行实现修改功能
4.查询员工-分页查询
路径: /emps 请求方式 : GET
这个功能我们可以分成几步 :
- 接收分页参数 page , pageSize , 调用service 进行分页查询,获取pageBean , 并响应
- 调用mapper接口,获取总记录数 total , 数据列表 rows , 封装 pageBean对象并返回
- mapper访问层需要 两条select 语句来查询 total 和 分页结果
新建pojo/PageBean类
package com.kitten.pojo;
import ...
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
// 这个bean对象不是自己随便建立的,要看接口文档的数据,不过一般都是封装了总数和数据集合list
private Long total;
private List rows;
}
com.kitten/Controller/EmpController
package com.kitten.controller;
import ...
/**
* 员工管理Controller
*/
@Slf4j
@RestController
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping("/emps") //前端如果没有传递参数,设置默认值1,5
public Result page(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "5")Integer pageSize){
log.info("分页查询:{},{}",page,pageSize);
PageBean pageBean = empService.page(page,pageSize);
return Result.success(pageBean);
}
}
com.kitten/Service/EmpService
package com.kitten.service;
import com.kitten.pojo.PageBean;
public interface EmpService {
PageBean page(Integer page, Integer pageSize);
}
com.kitten/Service/Impl/EmpServiceImpl
package com.kitten.service.impl;
import ...
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public PageBean page(Integer page, Integer pageSize) {
// 获取总记录数
Long total = empMapper.getTotal();
// 获取分页列表 , start表示从第几条开始查询;比如传递 (3,5)那么我要从第10条开始查,就包含11,12,...,15
Integer start = (page - 1)* pageSize;
// rows是获取到的emp的列表
List rows = empMapper.page(start, pageSize);
// 封装pagebean对象
PageBean pageBean = new PageBean(total, rows);
return pageBean;
}
}
com.kitten/Mapper//EmpMapper
package com.kitten.mapper;
import ...
@Mapper
public interface EmpMapper {
/**
* 查询记录总数
* @return
*/
@Select("select count(*) from tlias.emp")
public Long getTotal();
/**
* 分页查询
*/
@Select("select * from tlias.emp limit #{start},#{pageSize}")
public List page(Integer start , Integer pageSize);
}
上述,我们就完成了一个基本的分页查询功能接口,但是像这种逻辑比较单一但是任务量又稍微大一点的功能接口,我们一般会借助第三方插件(第三方依赖)来实现,下面我们回滚到实现这个接口之前,借助分页插件pageHelper来实现
引入 : pom.xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
controller层不需要太多变动 , 因为主要是和前端交互的
com.kitten/controller/EmpController
...
@GetMapping("/emps")
public Result page(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "5")Integer pageSize){
log.info("分页查询:{},{}",page,pageSize);
PageBean pageBean = empService.pageHelp(page,pageSize);
return Result.success(pageBean);
}
com.kitten/Service/Impl/EmpServiceImpl
...
/**
* 使用pagehelper
*/
@Override
public PageBean pageHelp(Integer page, Integer pageSize) {
// 1.设置分页参数
PageHelper.startPage(page,pageSize);
// 2.执行查询
List emps = empMapper.pageHelper();
Page p = (Page) emps;
// 3.封装PageBean对象
PageBean pageBean = new PageBean(p.getTotal(), p.getResult());
return pageBean;
}
这里就不写 EmpService了 , 就是一个接口方法
com.kitten/Mapper/EmpMapper
...
/**
* 使用pagehelper实现分页查询
* 我们引入插件后,不用考虑limit,有插件来帮我们实现
*/
@Select("select * from tlias.emp")
public List pageHelper();
说一下分页插件做的事情 :
mapper层中 , 我们执行了一条简单的sql语句 , 分页插件会自动将 * 换成 count(*) , 这样就能获得总记录数; 插件还会对该 语句进行改造 , 在后面加上 limit ? , ? ; 根据 startPage()函数传递进来的参数 , 自行计算起始的索引和记录数 ; 这样在service 层中 , 执行查询时得到的结果 , 就不只是mapper层中写的那么简单了; 然后我们要将得到的结果 封装到 page对象中 (因为page对象中有获取结果的方法) 从page对象中我们拿到 数据,并 创建 pageBean , 就可以了
写完之后可以测试一下
5.条件分页查询
路径: /emps 请求方式: GET
根据查询员工的姓名 , 性别 , 还有入职时间 进行条件分页查询
com.kitten/.../EmpConrtoller
...
/**
* 条件分页查询
* 根据查询员工的姓名 , 性别 , 还有入职时间 进行条件分页查询
*/
@GetMapping("/empcondition") //前端如果没有传递参数,设置默认值1
public Result pageCondition(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "5")Integer pageSize,
String name , Short gender,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
log.info("分页查询:{},{},{},{},{}",page,pageSize,name,gender,begin,end);
PageBean pageBean = empService.pageCondition(page,pageSize,name,gender,begin,end);
return Result.success(pageBean);
}
com.kitten/.../EmpServiceImpl
...
@Override
public PageBean pageCondition(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
// 1.设置分页参数
PageHelper.startPage(page,pageSize);
// 2.执行查询
List emps = empMapper.pageCondition(name, gender, begin, end);
Page p = (Page) emps;
// 3.封装PageBean对象
PageBean pageBean = new PageBean(p.getTotal(), p.getResult());
return pageBean;
}
com.kitten/.../EmpMapper
...
/**
* 条件分页查询
* xml映射
* @return
*/
List pageCondition(String name, Short gender, LocalDate begin, LocalDate end);
resources/.../EmpMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kitten.mapper.EmpMapper">
<select id="pageCondition" resultType="com.kitten.pojo.Emp">
select * from tlias.emp
<where>
<if test="name != null and name != ''">
name like concat("%",#{name},"%")
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
</mapper>
6.删除员工
路径: /emps/{ids} 请求方式:DELETE
样例: /emps/1,2,3
com.kitten/.../EmpController
/**
* 删除员工
* xml
*/
@DeleteMapping("/emps/{ids}")
public Result deleteEmps(List ids){
log.info("批量删除,ids:{}",ids);
empService.deleteEmps(ids);
return Result.success();
}
com.kitten/.../EmpServiceImpl
...
@Override
public void deleteEmps(List ids) {
empMapper.deleteEmps(ids);
}
com.kitten/.../EmpMapper
/**
* 删除员工
*/
void deleteEmps(List ids);
resources/.../EmpMapper.xml
...
<!-- 批量删除员工 -->
<delete id="deleteEmps">
delete from tlias.emp
where id in <!--(1,2,3) -->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
7.新增员工-文件上传
路径: /emps/{ids} 请求方式: POST
新增员工时我们会涉及到上传员工头像,这就属于文件上传功能
com.kitten/.../Empcontroller
/**
* 新增员工
*/
@PostMapping("/emps") //前端传递的json数据我们用emp来接收
public Result addEmp(@RequestBody Emp emp){
log.info("新增员工: {}", emp);
empService.addEmp(emp);
return Result.success();
}
com.kitten/.../EmpServiceImpl
@Override
public void addEmp(Emp emp) {
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
empMapper.insert(emp);
}
com.kitten/.../EmpMapper
/**
* 新增员工
* @param emp
*/
@Insert("insert into tlias.emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
"values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
void insert(Emp emp);
文件上传有两种存储形式 , 一种是本地存储 , 还有一种是云存储 , 这里我们选择阿里云OSS(对象存储服务) 云存储
先说下前端上传文件的几个要素 : 1. file类型的 input,用于选择上传的文件 ; 2,post请求方法 ; 3. form 必须指定 enctype="multipart/form-data"
后端是怎么接收文件的呢 ? springboot给我们提供了一个类 MultipartFile , 用来映射前端上传的文件
新建Controller/.../UploadController
package com.kitten.controller;
import ...
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload") // 参数名 和前端 form 中 input的 name 必须一致
public Result upLoad(String name , Integer age , MultipartFile image) throws IOException {
log.info("文件上传:{},{},{}", name,age , image);
// 前端发送数据到服务器时,服务器会把其当做临时文件tmp存储,服务器在返回响应之后是会把临时文件删除的,所以我们这里要把文件保存到服务器本地或云端
// 不使用原生文件名称,而使用uuid
// 先获取文件拓展名
String originalFilename = image.getOriginalFilename();
int i = originalFilename.lastIndexOf(".");
String extend = originalFilename.substring(i);
String newFileName = UUID.randomUUID().toString()+ extend;
// 1.将文件转存到服务器的磁盘目录中,此时还存在问题即:文件的名称是重复的时候,将会覆盖
image.transferTo(new File("E:\\files\\"+newFileName));
return Result.success();
}
}
postman发请求:
路径: http://localhost:8080/upload 请求方式 : POST Body : form-data 指定 file 类型,选择一张小图片
如果上传的文件过大,服务器会返回500 , 所以我们在服务器端配置下:
resources/.../application.properties
# config single uploaded file size
spring.servlet.multipart.max-file-size=10MB
# config multi uploaded files size
spring.servlet.multipart.max-request-size=100MB
对于文件上传我们一般不选择存储在本地 , 因为容量小, 无法解决报错等问题 , 一般是通过搭建FastDFS分布式文件存储系统或者MinIO这种对象存储服务
下面来介绍阿里云OSS
阿里云OSS,即阿里云对象存储服务 , 我们使用这种第三方服务的思路为 :
注册→充值→开通对象服务→创建bucket→获取AccessKey→参照官方SDK→集成
这块单开文档阿里云OSS使用 , 我们直接开始集成
8.文件上传-阿里云OSS集成
路径: /upload 请求方式: POST
创建包 utils , 再创建 utils/AliOSSUtils类
package com.kitten.utils;
import ...
/**
* 阿里云 OSS 工具类
*/
@Component
public class AliOSSUtils {
private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
// private String accessKeyId = "*********";
// private String accessKeySecret = "**********";
EnvironmentVariableCredentialsProvider credentialsProvider; //使用环境变量存储accesskeyId和accessKeySecret
{
try {
credentialsProvider = newEnvironmentVariableCredentialsProvider();
} catch (ClientException e) {
throw new RuntimeException(e);
}
}
private String bucketName = "your-bucket-name";
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint,credentialsProvider);
ossClient.putObject(bucketName, fileName, inputStream);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}
这块代码还是比较死的,在之后我们使用多坐标maven工程时候可能会做一些配置上的修改
这里还要解释几点 : 要给这个工具类加上 @Component 注解, 交给IOC容器管理 , 这样在使用的时候就直接注入就行
controller/.../UploadController
package com.kitten.controller;
import ...
@Slf4j
@RestController
public class UploadController {
@Autowired
private AliOSSUtils aliOSSUtils;
...
/**
* 阿里云OSS
*/
@PostMapping("/upload")
public Result upload(MultipartFile image) throws IOException {
log.info("文件上传阿里云OSS,{}",image.getOriginalFilename());
String url = aliOSSUtils.upload(image);
return Result.success(url);
}
}
运行我们的web服务 , 然后postman上传文件测试下就行
9.查询回显
查询
路径: /emps/{id} 请求方式: GET
代码这里不写了,比较简单
回显
路径: /emps 请求方式: PUT 请求参数: application/json
com.kitten/.../Empcontroller
@PostMapping("/emps")
public Result update(@RequestBody Emp emp){
log.info("更新员工:{}",emp);
empService.update(emp);
return Result.success();
}
com.kitten/.../Service/impl/EmpServiceImpl
@Override
public void update(Emp emp) {
emp.setUpdateTime(LocalDateTime.now());
empMapper.update(emp);
}
com.kitten/.../EmpMapper
void update(Emp emp);
com.kitten/.../EmpMapper.xml
<update id="update">
update tlias.emp
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
...
<if test="updateTime != null">
update_time = #{updateTime}
</if>
</set>
where id = #{id}
</update>
前端点击新增员工按钮时 , 填写数据并选择要上传的图片后 , 就会发起请求 , 后端web服务接收请求后就会吊起阿里云OSS对象存储服务 , 将图片上传到阿里云 , 然后阿里云将url返回给服务端 , 服务端再将url返回给用户端 , 用户端就能显示出图片 , 然后前端点击保存时 , 服务器就会将这条员工数据存到数据库
10.配置文件-参数配置化
在使用阿里云OSS对象存储服务时 , 我们是通过硬编码的方式 , 直接创建了类 AliOSSUtils , 并在类中配置了 endpoint , accesskey_id , accesskey_secret等配置信息 , 那如果以后的业务中 , 每涉及到一个第三方服务 , 就会有两个问题 : 1.如果参数变化了 , 我们就要重新编译成 class字节码文件 , 然后再重新运行,很繁琐 2.开发大项目时 , 实现类有很多很多,这些参数分散的地定义在 项目中 , 如果要修改参数,我们就要慢慢来定位 , 并修改和编译 , 不方便集中管理和维护
所以我们要将第三方服务的配置定义在 application.properties文件中
...
# ali cloud OSS service
aliyun.oss.endpoint="https://oss-cn-beijing.aliyuncs.com"
aliyun.oss.bucketName="web-kitten-tlias"
AliOSSUtils
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
这样配置完成后,就不用在java类中修改属性值 , 直接该配置文件就行
yml配置文件
是一种新的配置文件形式
yml基本语法 :
- 大小写敏感
- 数值前要有空格
- 使用缩进表示层级关系
- 相同层级的左侧对齐
# 定义对象
obj:
name: kitten
age: 18
# 定义List
hobby:
- java
- python
任务 : 将 application.properties 中的配置项替换到 application.yml中 , 两个文件在同一级目录
@ConfigurationProperties 注解
之前我们在项目中使用 @Value 来注入配置信息 , 但是当配置信息过多的时候 , 就要写很多个@Value , 所以我们可以使用 @ConfigurationProperties 来实现自动注入 , 前提是类中的 属性名称 和 配置文件中的 键名 相同 , 而且还要为 实体类 中的 属性 提供get/set 方法 , 并且 要将实体类加上 @Component 注解,交给IOC 容器管理 ; 然后使用@ConfigurationProperties(prefix="…") 来指定配置文件中 的前缀
新建utils/AliOSSproperties 实体类
package com.kitten.utils;
import ...
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSproperties {
private String endpoint;
private String bucketName;
}
application.yml
这个上面有 bucketname , endpoint 等等基本配置信息
utils/AliOSSUtils
package com.kitten.utils;
import ...
/**
* 阿里云 OSS 工具类
*/
@Component
public class AliOSSUtils {
@Autowired
private AliOSSproperties aliOSSproperties;
EnvironmentVariableCredentialsProvider credentialsProvider;
{
try {
credentialsProvider = newEnvironmentVariableCredentialsProvider();
} catch (ClientException e) {
throw new RuntimeException(e);
}
}
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
String endpoint = aliOSSproperties.getEndpoint();
String bucketname = aliOSSproperties.getBucketName();
...
}
}
pom.xml
...
<!-- 显示配置文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
那么,springboot基础我们就到这里,我们做了一个十分基础的后端项目,也熟悉了springboot的作用和开发中的业务功能,之后我们将会了解springboot出IOC外同等重要的概念 : AOP切面开发 , 同时也会学习事务注解等等操作 , 感谢大家支持。

Comments NOTHING