SpringBoot基础

kitten 发布于 2024-05-12 252 次阅读


Spring Boot是一个开源的Java框架,旨在简化Spring应用程序的开发过程。

Spring Boot的主要特点包括:

  1. 自动配置:根据项目的依赖自动配置Spring应用程序,减少了手动配置的复杂性。
  2. 独立运行:可以创建独立的Java应用程序,内嵌Tomcat、Jetty等服务器,无需外部服务器支持。
  3. 生产就绪:提供了多种生产环境的特性,如监控、健康检查和外部配置等。
  4. 简化的构建:通过Spring Initializr等工具,快速生成项目结构和依赖配置。
  5. 广泛的社区支持:拥有活跃的社区和丰富的文档,便于开发者获取帮助和资源。

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 超文本传输协议 , 规定了浏览器和服务器之间数据传输的规则

三大特点 :

  1. 基于TCP协议 : 面向连接 , 安全
  2. 基于请求-响应模型
  3. 无状态的协议 , 对于事务处理没有记忆能力

HTTP-请求数据格式 : 请求行/头/体

两种基本请求方式 :

  1. get , 请求参数在请求行中, 没有请求体
  2. 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

就是对同一个参数不同值我们使用&连接

那么我们后端接收这个请求的 接收方式 有下面两种 :

  1. 数组
    
    @RequestMapping("/arrayParam_spring")
    public String arrayParam(String[] hobby){
        System.out.println("hobby"+ Arrays.toString(hobby));
        return "ok";
    }
    
  2. 集合
    
    @RequestMapping("/listParam_spring")
    public String arrayParam_list(@RequestParam List hobby){
        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

如何分层解耦 ? 简单说下步骤 :

  1. service 层 及 dao 层的实现类 , 交给 IOC 容器管理 ( 用注解 @Component)
  2. 为 Controller 及 Service 注入运行时所依赖的对象 ( 用注解 @Autowired )
  3. 运行测试

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)

连接信息 :

  1. driver 驱动
  2. url  数据库连接的url
  3. 用户名 + 密码

上手 : 新建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表下部门的管理

前期准备

  1. 创建项目,勾选web , mybatis, spring web,lmbok,mysql driver 选项
  2. 配置文件信息 :`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
    
  3. 创建工程文件 :
    1. `com/kitten`下创建四个包 : mapper , service , pojo , controller
    2. `pojo`下创建两个实体类Emp和Dept , 对应要管理的表的emp和dept , 实体类的属性可以看着后面的写
    3. `controller`下创建两个类 , 一个EmpController , 一个DeptController
      
      package com.kitten.controller;
      import org.springframework.web.bind.annotation.RestController;
      /**
       * 员工管理Controller
       */
      @RestController
      public class EmpController {}
      // deptcontroller也类似
                
    4. `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 {}
      
    5. `mapper`层下创建两个接口 `deptMapper`和`empMapper`,该层用于访问数据库
    6. 创建数据库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();
    }
}

先写一个接口的架子 , 不处理逻辑 , 说一下这个架子中的一些点

  1. @Slf4j 注解为logback提供的注解,会自动为当前类提供日志log对象
  2. @RestController包含@ResponseBody注解 , 会将返回对象(Result)转换为json格式并响应
  3. 方法上面可以通过@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

这个功能我们可以分成几步 :

  1. 接收分页参数 page , pageSize , 调用service 进行分页查询,获取pageBean , 并响应
  2. 调用mapper接口,获取总记录数 total , 数据列表 rows , 封装 pageBean对象并返回
  3. 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();
    }
}

路径: 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基本语法 :

  1. 大小写敏感
  2. 数值前要有空格
  3. 使用缩进表示层级关系
  4. 相同层级的左侧对齐
# 定义对象
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切面开发 , 同时也会学习事务注解等等操作 , 感谢大家支持。