SpringBoot笔记

补充

1.名词补充:Java数据库连接(JDBC)

一、 前置工作

0

  • 创建项目时勾选Web中的SpringWeb选项,引入web依赖
  • 勾选SQL中的Mybatis选项,引入Mybatis依赖

1

项目创建成功后,在pom.xml文件中添加需要引用的库


添加RestController适用于前后端分离

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

然后在java文件里通过注解来引入

package com.example.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Demo1Application {

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

}

默认端口是8080,可以通过localhost:8080来访问

2

SpringBoot后端一般分为四层,由外至内逐层调用(从上到下)

  • 控制层(Controller Layer):接收前端(如Web、App、小程序)的HTTP请求,解析参数,调用业务层处理,最后封装数据返回响应。
  • 业务逻辑层(Service Layer):整个应用的核心,实现具体的业务逻辑。它协调多个数据访问层(Repository)的调用,完成一个完整的业务操作(可能涉及事务)。
  • 数据持久层/仓库层(Repository / Dao Layer)(也叫Mapper层):负责与数据库进行直接交互,完成数据的增(Create)、删(Delete)、改(Update)、查(Retrieve)操作。
  • 模型/实体层(Model / Entity Layer):封装数据。这一层不是严格意义上的“活动层”,而是一系列代表数据的Java类

src/main/java/com.example.demo1/目录下新建controllerservicemapper文件夹对应各层

3

在业务中,一般一个实体类都对应一个接口(为了较好的可扩展性)

二、 常用功能

  • @RequestMapping:配置路由

package com.example.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Demo1Application {

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

	@RequestMapping("/hello")
	public String hello(){
		return "Hello World";
	}
}

此时可以通过localhost:8080/hello来访问该函数

同样的,也可以给一个类配置路由

package com.example.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController

@RequestMapping("/api")
public class Demo1Application {

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

	@RequestMapping("/hello")
	public String hello(){
		return "Hello World";
	}

}

此时,用以访问该函数的地址变为了localhost:8080/api/hello

  • @GetMapping:处理GET请求

用法1 自动判断

该注解可以根据请求类型自动匹配

@GetMapping
public String getRequest(){
	return "GET请求已被正常处理";
}
// 函数的访问地址为localhost:8080/

用法2 设置路由

该注解也可以用来设置路由

@GetMapping("/get")
public String getRequest(){
	return "GET请求已被正常处理";
}
// 函数的访问地址为localhost:8080/get

用法3 以restful的形式通过url来传递参数

@GetMapping("/{id}")
public String getRequest(@PathVariable Long id){
	System.out.println(id);
	return "后端接收到了:"+id;
}

用法4 以传统url字符的方式传递参数

// 传统url符号传值就不需要配置路由的变量了
@GetMapping
public String normoalURL(@RequestParam Long id,@RequestParam String name){
	return "传统url符号传参以触发id:"+id+"\tname:"+name;
}
// localhost:8080/?id=1&name=abc
  • @PostMapping:处理POST请求

@PostMapping
public String save(@RequestBody Map<String,String> map){
	System.out.println(map.toString());
	return "POST请求接收成功";
}
  • @PutMapping:处理PUT请求

@PutMapping("/{id}")
public String update(@PathVariable Long id,@RequestBody Map<String,String> map){
	// 使用%s打印是因为通用性更强
	System.out.printf("ID=%s,name=%s",id,map.toString());
	return "PUT请求成功";
}
  • @DeleteMapping:处理delete请求

@DeleteMapping("/{id}")
public String delete(@PathVariable Long id){
	System.out.printf("ID=%s\n",id);
	return "DELETE请求接收成功";
}
  • @PathVariable:Restful形式接收参数的注解

  • @RequestParam:传统url符号形式接收参数的注解


<!-- 可以设置默认值 -->
@RequestParam(defaultValue = "xxx")
  • @RequestBody:通过POST方法接收参数的注解

  • @ComponentScan:当没有设置basepackages时,默认会将当前注解所在类的包当做是扫描包

三、利用URL传值的两种形式

  • Restful形式:在url后直接跟上属性的值,多个值之间用/隔开。示例


    localhost:8080/6(id)/zhangsan(name)
  • 传统url符号形式:在url后面通过?来连接变量名,多个变量之间用&连接。示例


    localhost:8080/path?key1=value1&key2=value2

四、修改配置文件

1.修改SpringBoot启动时的水印

src/main/resources目录下创建banner.txt。并在文本文档中自定义水印的内容

2.修改全局配置

SpringBoot提供了多种属性配置方式

application.properties

server.port=9090
server.servlet.context-path=/start

application.yml/.yaml

  1. src/main/resources下的application的后缀名修改为.yml
  2. 配置格式如下
server:
  servlet:
    context-path: /api #配置全局路由
  port: 8080 #配置端口,默认为8080

3.快速切换全局配置

  1. src/main/resources/目录下新增文件application-[dev自定义名字].yml(可以多创建几个用来切换)
  2. 在新增的文件中编辑
server:
  servlet:
    context-path: /api #配置全局路由
  port: 9090 #配置端口,默认为8080
  1. 在原始的application.yml中编辑
server:
  profiles:
    active: dev #通过修改active可以实现快速切换配置文件

4.yml配置文件的书写与获取

在yml配置文件里写的可以在java中导入

email:
    user: xxxx@qq.com
    code: abcdf
    host: smtp.qq.com
    auth: true

# 值也可以是数组,每个元素见用-分隔
hobbies:
    - 打篮球
    - 打游戏
    - 打羽毛球
  • 值前边必须有空格,作为分隔符
  • 使用空格作为缩进表示层级关系

字符串不用加双引号

引入值:

@Component
public class EmailProperties{
    // 依靠这个注解来实现
    @Value("${email.user}")
    public String user;
}
// 还可以添加前缀,来减少value需要写的层级
@ConfigurationProperties(prefix="email")
@Component
public class EmailProperties{
    // 依靠这个注解来实现
    @Value("${user}")
    public String user;
}

还可以添加在方法的参数里

public Country country(@Value("${country.name}") String name){
    ...
}
  • @Value("${键名}")
  • @ConfigurationProperties(prefix=“前缀”)

五、MySQL数据库驱动

基本操作

1. pom.xml 依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

2. application.yml 配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC
    username: root
    password: password

mybatis:
  type-aliases-package: com.example.demo.entity

3. 创建数据库表

CREATE TABLE user (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50),
  age INT
);

4.实体类

src/main/java/com/example/demo/entity/User.java

package com.example.demo.entity;

public class User {
    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}

5.MyBatis Mapper

src/main/java/com/example/demo/mapper/UserMapper.java

package com.example.demo.mapper;

import com.example.demo.entity.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM user")
    List<User> findAll();

    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(Integer id);

    @Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);

    @Update("UPDATE user SET name=#{name}, age=#{age} WHERE id=#{id}")
    int update(User user);

    @Delete("DELETE FROM user WHERE id = #{id}")
    int delete(Integer id);
}
注解 作用
@Select 查询语句
@Insert 插入
@Update 修改
@Delete 删除
@Options(useGeneratedKeys=true) 自动回填自增 ID

6.Controller(提供 REST 接口)

src/main/java/com/example/demo/controller/UserController.java

package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

    private final UserMapper userMapper;

    public UserController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @GetMapping("/all")
    public List<User> findAll() {
        return userMapper.findAll();
    }

    @GetMapping("/{id}")
    public User findById(@PathVariable Integer id) {
        return userMapper.findById(id);
    }

    @PostMapping("/add")
    public String add(@RequestBody User user) {
        userMapper.insert(user);
        return "添加成功";
    }

    @PutMapping("/update")
    public String update(@RequestBody User user) {
        userMapper.update(user);
        return "更新成功";
    }

    @DeleteMapping("/delete/{id}")
    public String delete(@PathVariable Integer id) {
        userMapper.delete(id);
        return "删除成功";
    }
}

7.接口测试

操作 方法 URL Body
查询全部 GET localhost:8080/user/all
查询单个 GET localhost:8080/user/1
添加 POST localhost:8080/user/add {"name":"Tom","age":18}
修改 PUT localhost:8080/user/update {"id":1,"name":"Jack","age":22}
删除 DELETE localhost:8080/user/delete/1

MyBatisPlus持久层框架+Lombok减少样板代码

1.导入MyBatisPlus持久层框架

  1. pom.xml中引入驱动
<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<version>9.3.0</version>
</dependency>
  1. application-dev.yml中添加以下配置
spring:
  datasource: #创建数据源
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/database(数据库名称)
    username: root
    password: password
mybatis-plus: #MyBatisPlus持久层框架
  mapper-locations: classpath:mapper/*.xml

url额外配置项:

url: jdbc:mysql://localhost:3306/0925-demo?allowPublicKeyRetrieval=true&useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai

url解析:

  • jdbc:mysql://用 JDBC 驱动连接 MySQL

  • localhost:3306数据库地址和端口

  • 0925-demo要连接的数据库名

  • allowPublicKeyRetrieval=true → 解决 MySQL 8 公钥验证问题

  • useUnicode=true → 开启 Unicode 支持

  • useSSL=false → 禁用 SSL(开发环境常用)

  • characterEncoding=utf8 → 设置字符集(建议改成 utf8mb4)

  • serverTimezone=Asia/Shanghai → 指定时区,避免时间错乱

  1. DemoApplication.java里引入注解
@SpringBootApplication
@RestController
@MapperScan("com.example.demo1.mapper") //新添加的内容,括号里的地址为你建立的持久层软件包地址

2.导入Lombok依赖

Lombok作用分析

Lombok 依赖,它在 Spring Boot 项目里主要用来 减少样板代码(getter/setter/构造器/toString 等)。

<!--Lombok 依赖,它在 Spring Boot 项目里主要用来 减少样板代码(getter/setter/构造器/toString 等)。-->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

3.配置并编写实体类

创建java/com.example.demo1/entity/实体类文件夹
并在其中创建UserJava类

package com.example.demo1.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("`user`")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

注:@TableName 用于指定实体类对应的 数据库表名。程序会用user生成SQL,例如

SELECT id, name FROM `user` WHERE id = 1;

4. 使用实体类

  1. mapper软件包里创建UserMapper
  2. 写入框架,在里面可以写一些增删改查功能
package com.example.demo1.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo1.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {

}
  1. 来到test/java/项目名/Demo1ApplicationTests.java测试文件中,写入以下代码并导入相应类
    @Resource
	private UserMapper userMapper;

	@Test
	void contextLoads() {
		// 查询数据库
		System.out.println(("----- selectAll method test ------"));
		List<User> userList = userMapper.selectList(null);
		Assert.isTrue(5 == userList.size(), "");
		userList.forEach(System.out::println);
	}

六、Spring Bean 注册 & 注入 笔记

  • Bean:由 Spring 容器管理的对象,负责创建、依赖注入和生命周期管理。
  • 实例化(Instantiation):用 new 创建对象。
  • 注入(Injection / DI):Spring 容器把 Bean 提供给其他对象使用,可以复用已有 Bean,也可能新建(取决于作用域)。
  • @Autowired:Spring 注解,默认按类型注入,可用 @Qualifier@Primary 解决歧义,可选注入用 required=false
  • @Resource:JSR-250 注解,默认按名字注入,找不到再按类型回退,跨框架兼容性好。
  • 使用建议:构造器注入最好,字段注入仅适合快速实验或小项目。

⚡ 小提示:实例化 ≠ 注入,注入可能是复用已有 Bean,而不是每次 new 对象。


(1)Bean的注入

1. @Autowired 按类型注入

@Component
public class UserService {
    public void hello() {
        System.out.println("Hello from UserService");
    }
}

@Component
public class MyController {

    @Autowired
    private UserService userService;  // 按类型注入

    public void run() {
        userService.hello();
    }
}
  • @Qualifier("BeanName")指定具体Bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class MyServiceA {
    // 按类型注入,但因为有多个 GreetingService,需要用 @Qualifier 指定具体 bean
    @Autowired
    @Qualifier("englishGreeting")
    private GreetingService greeting;

    public void say() {
        System.out.println(greeting.greet()); // 输出 "Hello"
    }
}

@AutoWired自动注入逻辑:先按类型匹配(bytype)若有多个,再按名字匹配(byname)。但要注意,按名字匹配是一种兜底行为,在5.x版本以下会直接报错。

注:在存在同一类型多个Bean名字时,可以在注册Bean时使用@Primary注解来指定该情况时的默认Bean。但请注意,该方法可能会因为Bean的扫描顺序而无法达成预期的结果

2. @Resource 按名称优先

@Component("userService")
public class UserService {
    public void hello() {
        System.out.println("Hello from UserService");
    }
}

@Component
public class MyController {

    @Resource(name = "userService")   // 按名称注入
    private UserService service;

    public void run() {
        service.hello();
    }
}

@Autowired vs @Resource 匹配规则

@Autowired

  1. 按类型 查找 Bean
  2. 如果有多个同类型 Bean → 按名称匹配(字段名 / 参数名)
  3. 如果仍然不唯一 → 需要配合 @Qualifier@Primary
  4. 可选注入:@Autowired(required = false)作用:告诉 Spring:这个依赖“能注入就注入,注不进来也别报错”。注入失败则值为null

@Resource

  1. 如果写了 @Resource(name="xxx")强制按名称 查找
  2. 如果没写 name
    • 先按 字段名 查找 Bean
    • 如果没找到 → 按 类型 查找
    • 如果有多个同类型 Bean → ❌ 报错

(2)Bean的注册

方法A:注解注册(最常用)

import org.springframework.stereotype.Component;

@Component
public class MyBean {
    public void hello() {
        System.out.println("Hello, Spring Bean!");
    }
}
  • @Component 注解告诉 Spring,这个类要被管理为 Bean。@Component("name")可以指定Bean的名字

  • Spring 容器启动时,会扫描并注册这个 Bean。

注解 说明 位置
@Component 声明bean的基础注解 不属于以下三类时,用此注解
@Controller @Component的衍生注解 标注在控制器类上
@Service @Component的衍生注解 标注在业务类上
@Repository @Component的衍生注解 标注在数据访问类上(由于与mybatis整合,用得少)

如果要注册的Bean对象来自于第三方(不是自定义的),则无法用@Component及其衍生注解的
第三方包用@Bean和@import注解,详细用到时再ai补充

方法B:配置类注册

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        return new MyBean();  // 手动创建 Bean
    }
}
  • 使用 @Bean 注解的方法返回对象,Spring 自动将其纳入容器管理。

  • 好处:可以控制对象创建逻辑。

方法C:使用工厂Bean注册——实现FactoryBean接口

FactoryBean 的作用是:
让一个 Bean 「专门负责生产另一个 Bean」
Spring 注入和获取到的,默认是“生产出来的对象”,而不是工厂本身。
可以用来在工厂Bean的内部实现自定义操作,常用于与第三方库对接等场景

实现:

  • 需要有一个Bean实现FactoryBean,这个Bean就成为了一个特殊的Bean(工厂Bean)
  • 需要实现两个方法:
    • getObject():指定OrderService所指向的Bean名(根据Bean名获取时)
    • getObjectType():指定OrderService所指向的Bean类型(根据Bean类型获取时)
//com.example.c3_ico.service.OrderService
@Service  
public class OrderService implements FactoryBean {  
    @Autowired  
    private IUserDao userDao;  
  
    @Override  
    //指定OrderService所指向的Bean名(根据Bean名获取时)
    public @Nullable Object getObject() throws Exception {  
        return new UserService(userDao);  //返回UserService对象
    }  
  
    @Override  
    //指定OrderService所指向的Bean类型(根据Bean类型获取时)
    public @Nullable Class<?> getObjectType() {  
        return UserService.class;  
    }  
}

解释: 通过IOC容器获取OrderService时,实际获取到的是UserService对象。(即:Spring 注入和获取到的,默认是“生产出来的对象”,而不是工厂本身。)

注:run.getBean("OrderService")获取到的是该工厂所生产的对象。要获取该工厂对象本身,可以用run.getBean(OrderService.class)的方式通过类型来获取。
或通过run.getBean("&OrderService")来获取它实际的Bean

(3)Bean的注册条件设置

SpringBoot提供了设置注册生效条件的注解@Conditional

注解 说明
@Conditional(xxx.class) 必须指定一个实现了Condition接口的类,由matches方法的返回值决定当前Bean是否生效。
@ConditionalOnProperty 配置文件中存在对应的属性,才声明该bean
@ConditionalOnMissingBean 当不存在当前类型的bean时,才声明该bean
@Conditional0nclass 当前环境存在指定的这个类时,才声明该bean

注:@ConditionalOnProperty(prefix="前缀名",name={"value1","value2"})

七、Bean的实例化

目录结构:

  • service
    • UserService.java
  • dao
    • UserDao.java
  • config
    • SpringConfig.java

注:此处按优先级排序,1为最优先

1.无参构造函数

//UserService.java
@Service  
public class UserService implements IUserService{  
  
    @Autowired  
    IUserDao userDao;  
  
    public UserService(){  
        System.out.println("UserService无参构造函数实例化");  
    }  
  
    @Override  
    public void getUser() {  
        userDao.getUser();  
        System.out.println("userDao的方法已被执行");  
    }  
}

2.有参构造函数

//UserService.java
@Service  
public class UserService implements IUserService{  
  
    @Autowired  
    IUserDao userDao;  
  
    public UserService(IUserDao userDao) {  
        System.out.println(userDao);  
        System.out.println("UserService无参构造函数实例化");  
    }  
  
    @Override  
    public void getUser() {  
        userDao.getUser();  
        System.out.println("userDao的方法已被执行");  
    }  
}

注:Spring也会自动在容器中寻找参数并注入
注:有多个有参构造函数(并且没有无参构造函数)时会报错

3.手动指定某个构造参数

此时该类中存在多个有参构造函数。且没有被注册为Bean

//UserService.java
//@Service  
public class UserService implements IUserService{  
  
    @Autowired  
    IUserDao userDao;  
  
    public UserService(IUserDao userDao) {  
        System.out.println("UserService的有参构造函数实例化:"+userDao);  
    }  
  
    public UserService(IUserDao userDao, SpringConfig springConfig) {  
        System.out.println("UserService有参构造函数实例化"+userDao+springConfig);  
    }  
  
    @Override  
    public void getUser() {  
        userDao.getUser();  
        System.out.println("userDao的方法已被执行");  
    }  
}
  1. 在config类中利用@Bean来指定构造函数
//SpringConfig.java

@Configuration  
//@Import(MyImportBeanDefinitionRegistrar.class)  
public class SpringConfig {  
    /*通过@Bean的方式手动指定执行哪个构造函数*/  
    @Bean  
    public UserService userService(UserDao userDao){  
        UserService userService = new UserService(userDao);  
        return userService;  
    }  
}
  1. 在对应构造函数上方使用@AutuWired配置默认构造函数
@Service  
public class AutoWiredServiceTest {  
      
    @Autowired  
    public AutoWiredServiceTest(IUserDao userDao) {  
        System.out.println(userDao);  
    }  
  
    public AutoWiredServiceTest(IUserDao userDao, IUserService userService) {  
        System.out.println(userService.toString()+userDao.toString());  
    }  
}

八、设置统一返回的结果类(为方便接收,将格式统一)

  1. 在项目中创建common
  2. 在包下创建Result
  3. 写入以下代码
package com.example.demo1.common;

import lombok.Data;

import java.io.Serializable;


/**
 * 返回统一结果类
 */
@Data
public class Result implements Serializable {
    private static final long serialVersionUID = 1L;
    //    状态码
    private int code;
    //    提示信息message
    private String msg;
    //    返回的属性类型
    private Object data;

    /**
     * 直接返回成功结果
     * @param data
     * @return
     */
    public static Result success(Object data) {
        return success(200, "操作成功", data);
    }

    /**
     * 自定义返回成功结果
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static Result success(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }

    /**
     * 不带结果直接返回成功。适合不需要向前端传值的时候使用
     * @return
     */
    public static Result success() {
        Result r = new Result();
        r.setCode(200);
        r.setMsg("操作成功");
        return r;
    }

    /**
     * 直接返回失败信息
     * @return
     */
    public static Result error() {
        return error(400, "操作失败", null);
    }

    /**
     * 带参数返回失败信息
     * @param msg
     * @return
     */
    public static Result error(String msg) {
        return error(400, msg, null);
    }

    /**
     * 自定义返回失败信息
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static Result error(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }
}

九、在控制层调用这些类示例

  1. contorller下新建UserController
  2. 写入以下代码,作用是实现基本的增删改查
package com.example.demo1.controller;

import com.example.demo1.entity.User;
import com.example.demo1.common.Result;
import com.example.demo1.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {
    //注入
    @Resource
    private UserService userService;

    // 新增用户
    @PostMapping
    public Result save(@RequestBody User user){
        userService.save(user);
        return Result.success();
    }

    /**
     * 修改用户
     * @param user
     * @return
     */
    @PutMapping
    public Result update(@RequestBody User user){
        userService.updateById(user);
        return Result.success();
    }

    /**
     * 查询单个用户
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result getOne(@PathVariable Long id){
        return Result.success(userService.getById(id));
    }

    /**
     * 查询所有用户
     * @return
     */
    @GetMapping
    public Result list(){
        return Result.success(userService.list());
    }
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Long id){
        userService.removeById(id);
        return Result.success();
    }
}

十、思路逻辑整理:

1.控制层怎么实现对数据库进行增删改查的?

控制层

控制层调用更下一层的业务逻辑层,业务逻辑层存放了了对数据库的这些方法的实现

业务逻辑层

在业务逻辑层service/UserService中通过自定义UserService接口继承IService<T>接口来继承了MyBatis-Plus中已经预留好的接口。其接口内部定义了一些针对数据库操作的方法。

package com.example.demo1.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo1.entity.User;

/**
 * https://www.baomidou.com/guides/data-interface/
 * IService 是 MyBatis-Plus 提供的一个通用 Service 层接口,它封装了常见的 CRUD 操作,包括插入、删除、查询和分页等。通过继承 IService 接口,可以快速实现对数据库的基本操作,同时保持代码的简洁性和可维护性。
 *
 * IService 接口中的方法命名遵循了一定的规范,如 get 用于查询单行,remove 用于删除,list 用于查询集合,page 用于分页查询,这样可以避免与 Mapper 层的方法混淆。
 */
public interface UserService extends IService<User> {
}

此处只继承了接口,但接口并不能实现其中的方法。

在业务逻辑层service/impl/UserServiceImpl中,自定义一个UserServiceImpl类,继承MyBatis-Plus 提供的ServiceImpl<M,T>抽象类与自定义的UserService接口,其抽象类内部已经实现了数据库增删改查的基本方法。用户也可以在自定义类里添加额外的方法。

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    // 已经实现了基本方法。如果有需要还可以在这里添加新的方法
}

持久层

解析ServiceImpl<M,T>抽象类:

  • M:表示 Mapper 类型,必须是 BaseMapper<T> 的子类
  • T: 表示实体类类型(数据库表对应的 Java 类)

ServiceImpl<UserMapper, User>

  • M = UserMapper
    → 说明这个 Service 要使用的 MapperUserMapper,而 UserMapperBaseMapper<User> 的子接口。(持久层)

  • T = User
    → 说明这个 Service 要操作的实体类是 User

所以,ServiceImpl<UserMapper, User> 里的尖括号,就是 具体化泛型参数 的过程。

mapper/UserMapper类中自定义了UserMapper类继承BaseMapper<User>

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

注:BaseMapper<User>原为BaseMapper<T>,User类是在entity/User中定义的用户实体类,如下

@Data
@TableName("`user`")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

十一、Lombok @Data 注解作用分析

@Data 是 Lombok 提供的一个组合注解,用于自动生成实体类常用方法,减少重复代码。

它自动生成:

  • Getter / Setter
  • toString()
  • equals() / hashCode()
  • 构造方法

1. Getter / Setter 方法

功能

  • getter:获取字段值
  • setter:设置字段值

示例

User user = new User();

// 设置值
user.setName("Alice");
user.setAge(20);

// 获取值
System.out.println(user.getName()); // 输出: Alice
System.out.println(user.getAge());  // 输出: 20

作用:保证字段私有化(private),同时安全地访问和修改字段。


2. toString() 方法

功能

把对象转换成可读的字符串,用于打印和调试。

示例

User user = new User();
user.setId(1L);
user.setName("Alice");
user.setAge(20);
user.setEmail("alice@example.com");

System.out.println(user);
// 输出: User(id=1, name=Alice, age=20, email=alice@example.com)

作用:方便调试和日志记录对象内容。


3. equals() 和 hashCode() 方法

功能

  • equals(Object obj):判断两个对象是否相等(通常根据字段值判断)
  • hashCode():生成对象的哈希码(常用于集合如 HashMap、HashSet)

示例

User user1 = new User();
user1.setId(1L);
user1.setName("Alice");

User user2 = new User();
user2.setId(1L);
user2.setName("Alice");

System.out.println(user1.equals(user2)); // 输出 true

作用:方便对象比较和在集合中正确存储。


4. 构造方法

功能

  • 默认构造方法
  • 对于 final@NonNull 字段,会生成带参数构造方法

示例

User user = new User(); // 使用默认构造方法

作用:方便创建对象。


5. 总结表格

方法 作用 示例
getter 获取字段值 user.getName()
setter 设置字段值 user.setAge(20)
toString 打印对象信息 System.out.println(user)
equals 判断对象是否相等 user1.equals(user2)
hashCode 对象哈希值(用于集合) hashMap.put(user, value)
构造方法 创建对象 new User()

总结
@Data 让实体类更简洁,省去手动编写 getter/setter、toString、equals/hashCode 等常用方法,让你可以专注写业务逻辑。

十二、Mybatis-Plus中的数据分页功能

1.添加分页插件

  1. 引入Maven bom管理依赖到pom.xml中。详见官网.
    注意版本号要一致
<!-- 注意该项与<dependencies>标签平行 -->
<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-bom</artifactId>
			<version>3.5.14</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>
  1. 引入可选模块,引入代码也从官网复制
<!-- jdk 11+ 引入可选模块 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
  1. 创建config软件包
  2. config软件包下创建MyBatisPlasConfig类,并写入以下代码
// 从官网复制的,已经准备好的模板
// https://www.baomidou.com/plugins/pagination/
package com.example.demo1.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.example.demo1.mapper")
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
        return interceptor;
    }
}

2.在控制层的UserController中调用数据分页插件

/**
 * 用户分页
 * @param pageNum
 * @param pageSize
 * @return
 */
@GetMapping("/page")
public Result findPage(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize){
    return Result.success(
            userService.page(new Page<>(pageNum,pageSize))
    );
}

十三、自动填充字段(以create_time与update_time)为例

可以查阅自动填充相关文档

1 在数据库中创建create_timeupdate_time这两个字段

2 修改User实体类

package com.example.demo1.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("`user`")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    /**
     * 该注解的value值解决了变量名与数据库属性名不一致的问题
     * @TableField
     */
    @TableField(value = "create_time",fill = FieldFill.INSERT)//添加时自动填充
    private LocalDateTime createTime;
    @TableField(value = "update_time",fill = FieldFill.UPDATE)//修改时自动填充
    private LocalDateTime updateTime;
}

3 创建一个类来实现 MetaObjectHandler 接口,并重写 insertFillupdateFill 方法。

  • @JsonFormat@TableField
  1. 按照官网提示,先创建handler软件包
  2. 在该软件包下,创建与官网代码名字相同的(名字相同为了复制方便)MyMetaObjectHandler
  3. 粘贴官网代码并导入相应的包。并把原有的逻辑注释掉,写上自己的新逻辑
package com.example.demo1.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("`user`")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    /**
     * 该注解的value值解决了变量名与数据库属性名不一致的问题
     * @TableField
     */
    @TableField(value = "create_time",fill = FieldFill.INSERT)//添加时自动填充
    /**
     * 它的作用是 指定该字段在 JSON 序列化(转成 JSON 字符串)和反序列化(JSON 转回对象)时的日期时间格式。
     * 序列化(对象->json):当对象被返回给前端时LocalDateTime 类型的 createTime、updateTime 会被转换成字符串,格式遵循 yyyy-MM-dd HH:mm:ss。
     * 反序列化(json->对象):当前端传过来 JSON 数据时,Jackson 也会根据这个格式把字符串解析回 LocalDateTime 对象
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @TableField(value = "update_time",fill = FieldFill.UPDATE)//修改时自动填充
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
}

十四、在控制台打印日志信息

log.info("开始更新填充...");

十五、引入Redis数据库

1 在pom.xml中引入相关依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2 在Application-dev.yml中做好相关配置

server:
  servlet:
    context-path: /api #配置全局路由
  port: 8080 #配置端口,默认为8080
spring:
  datasource: #创建数据源
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/0925-demo?allowPublicKeyRetrieval=true&useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: password
  data:
    # redis配置
    redis:
      # Redis数据库索引(默认为0)
      database: 1
      # Redis服务器地址
      host: 127.0.0.1
      # Redis服务器连接端口
      port: 6379
      # Redis服务器连接密码(默认为空)
      password:
      # 连接超时时间
      timeout: 10s
      lettuce:
        pool:
          # 连接池最大连接数
          max-active: 200
          # 连接池最大阻塞等待时间(使用负值表示没有限制)
          max-wait: -1ms
          # 连接池中的最大空闲连接
          max-idle: 10
          # 连接池中的最小空闲连接
          min-idle: 0
mybatis-plus:
   #MyBatis持久层框架
  mapper-locations: classpath:mapper/*.xml

十六、IDEA中导入起步依赖通用教程

方法1:图形化页面导入

  1. 左上角文件>新建>模块
  2. 在弹出的页面里把需要的依赖都选上
  3. 创建模块,在.mvn/模块名/目录下
  4. 进入新建的pom.xml文件,复制需要的依赖到原本的pom.xml

十七、单设置状态码的方法

@GetMapping("/list")
public Result<String> list(@RequestHeader(name = "Authorization") String token, HttpServletResponse response) {
    //验证token,令牌验证
    try {
        Map<String, Object> claims = JwtUtil.parseToken(token);//报错就验证失败,正常执行就验证成功
    } catch (Exception e) {
        //http响应码为401
        response.setStatus(401);
        return Result.error("未登录");
    }
    return Result.success("所有的文章数据。。。");
}

十八、拦截器

所有限制登录权限的可以通过拦截器来访问。以拦截器抛出的布尔值来判断用户是否登录

1.拦截器的注册

新建.../interceptors/包,在其下创建java类LoginInterceptor.java

package com.itheima.interceptors;

import com.itheima.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Map;
//拦截器

@Component//将当前对象注入到IOC容器里
public class LoginInterceptor implements HandlerInterceptor {

    /*重写方法*/
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //通过request对象来拿到请求头,进而拿到某个属性
        String token = request.getHeader("Authorization");
        try{
            Map<String,Object> claims = JwtUtil.parseToken(token);
            //验证成功,应该放行
            return true;
        }catch (Exception e){
            //设置响应码
            response.setStatus(401);
            //不放行
            return false;
        }
    }
}

2.注册拦截器配置类

新建.../Config/包,并在其下新建java类WebConfig.java

package com.itheima.Config;

import com.itheima.interceptors.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration//将配置类注册到容器里
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //registry是一个注册器
        //addInterceptor把拦截器注册进去,默认拦截所有请求
        //目标:登录与注册不拦截。应该放行这两个接口
        //excludePathPatterns放行指定接口
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}

十九、前后端分离时Controller应有的操作

@RestController 前后端分离时声明返回数据(而非页面)
应在@Controller注解下添加@RestController注解,声明该Java类返回的是数据而非页面

二十、配置初始化回调方法

详见7.Bean的生命周期

在 Bean 的所有依赖属性都注入完成之后,执行一段初始化逻辑。

方式1:通过实现InitializingBean接口实现初始化回调方法

package com.example.springboot1.lifeCallBack;  
  
import org.springframework.beans.factory.InitializingBean;  
  
public class ChangChangService implements InitializingBean {  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        System.out.println("初始化");  
    }  
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
}

输出顺序为:

构造方法初始化
初始化

方式2:基于@PostConstruct注解实现初始化回调

public class ChangChangService implements InitializingBean {  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        System.out.println("通过实现接口初始化");  
    }  
  
    @PostConstruct  
    public void init(){  
        System.out.println("基于注解初始化");  
    }  
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
}

方法3:基于@Bean的initMethod属性实现初始化回调方法

ChangChangService.java

package com.example.springboot1.lifeCallBack;  
  
import jakarta.annotation.PostConstruct;  
import org.springframework.beans.factory.InitializingBean;  
  
public class ChangChangService implements InitializingBean {  
    public void init2() throws Exception{  
        System.out.println("基于initMethod初始化");  
    }  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        System.out.println("通过实现接口初始化");  
    }  
  
    @PostConstruct  
    public void init(){  
        System.out.println("基于注解初始化");  
    }  
  
  
  
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
}

lifeCallBackTest.java

package com.example.springboot1.lifeCallBack;  
  
import org.junit.jupiter.api.Test;  
import org.springframework.boot.test.context.SpringBootTest;  
import org.springframework.context.annotation.Bean;  
  
@SpringBootTest(classes = lifeCallBackTest.class)  
public class lifeCallBackTest {  
  
    @Bean(initMethod = "init2")  
    public ChangChangService changChangService(){  
        return new ChangChangService();  
    }  
  
    @Test  
    public void test(){  
        System.out.println("测试");  
    }  
}

注:当同时存在时初始化顺序:注解>接口>initMethod属性

为什么不能把初始化逻辑放在构造方法?

这是实现初始化回调的根本原因。

❌ 构造方法的问题

在构造方法执行时:

  • @Autowired 的 Bean 还没注入

  • @Value 的配置还没赋值

  • 可能为 null 或默认值

二十一、配置销毁回调方法

SpringBoot框架会在JVM运行完之后,会自动调用销毁方法。但Spring框架不会,需要自己手动调用close()方法进行销毁

注:销毁回调允许存在多个

方式1:通过实现DisposableBean接口实现销毁回调方法

public class ChangChangService implements DisposableBean {  
      
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
  
    @Override  
    public void destroy() throws Exception {  
        System.out.println("接口:销毁destroy");  
    }  
}

方式2:基于@PreDestroy 注解实现销毁回调

public class ChangChangService implements DisposableBean {  
  
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
  
    @Override  
    public void destroy() throws Exception {  
        System.out.println("接口:销毁destroy");  
    }  
  
    @PreDestroy  
    public void destroy2(){  
        System.out.println("接口:销毁destroy");  
    }  
}

方式3:基于@Bean的destroyMethod属性实现销毁回调方法

ChangChangService.java

public class ChangChangService implements DisposableBean {  
  
    public ChangChangService(){  
        System.out.println("构造方法初始化");  
    }  
  
    @Override  
    public void destroy() throws Exception {  
        System.out.println("接口:销毁destroy");  
    }  
  
  
    @PreDestroy  
    public void destroy2(){  
        System.out.println("注解:销毁destroy2");  
    }  
  
  
    public void destroy3(){  
        System.out.println("@Bean属性:销毁destroy3");  
    }  
}

lifeCallBackTest.java

@SpringBootTest(classes = lifeCallBackTest.class)  
public class lifeCallBackTest {  
  
    @Bean(destroyMethod = "destroy3")  
    public ChangChangService changChangService(){  
        return new ChangChangService();  
    }  
  
    @Test  
    public void test(){  
        System.out.println("测试");  
    }  
}

注:当同时存在时初始化顺序:注解>接口>initMethod属性

销毁回调的作用

销毁回调的作用是:在 Spring 容器关闭、Bean 即将被销毁时,给开发者一个“安全、可控”的机会去释放资源和做收尾工作。

二十二、在Spring中使用AOP(面向切口编程)

AOP是对OOP功能的扩展与增强

0.引入依赖

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-aop</artifactId>  
    <version>3.3.2</version>  
</dependency>

记录方法执行耗时功能:

package org.example.c4_aop;  
  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.springframework.stereotype.Component;  
  
@Aspect // 标记为切面类  
@Component  
public class LogAspect {  
    /*实现方法用时  切点表达式*/  
    @Around("execution(* org.example.c4_aop.UserService.*(..))")  
    public void log(ProceedingJoinPoint proceedingJoinPoint){  
        // 记录方法用时  
        /*获取当前时间*/  
        long begin = System.currentTimeMillis();  
  
        /*执行具体的方法*/  
        try {  
            proceedingJoinPoint.proceed();  
        } catch (Throwable e) {  
            System.out.println("方法执行异常"+e.getMessage());  
        }  
  
  
        long end = System.currentTimeMillis();  
        System.out.println("方法用时:" + (end - begin) + "ms");  
    }  
}

1. @Aspect 注解

  • 作用:标记一个类为 切面类(Aspect),告诉 Spring 这是一个包含横切逻辑(如日志、事务、安全等)的类。
  • 特点:
    • 必须配合 Spring 容器管理(通常加上 @Component)。
    • 内部可以定义通知(Advice)方法,通知可以在目标方法执行前、后、异常时执行。
@Aspect
@Component
public class LogAspect {
    // 切面逻辑放在这里
}

2. @Around 注解

  • 作用:环绕通知(Around Advice),可在 方法执行前后 做处理,还可以决定是否执行目标方法。
  • 语法:
@Around("切点表达式")
public Object 方法名(ProceedingJoinPoint joinPoint) { ... }

切点表达式解析

@Around("execution(* org.example.c4_aop.UserService.*(..))")
  • execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • 分解:
    1. * :匹配任意返回类型
    2. org.example.c4_aop.UserService.* :匹配 UserService 类的所有方法
    3. (..) :匹配任意数量、任意类型的参数

⚡ 小结:该表达式表示 对 UserService 类的所有方法进行环绕通知


3. ProceedingJoinPoint 和 proceed()

  • ProceedingJoinPoint
    • 是环绕通知专用的参数对象
    • 封装了当前被拦截的方法信息(方法名、参数、目标对象等)
    • 可以控制目标方法的执行
  • proceed()
    • 调用它才能执行 目标方法
    • 可以在调用前后增加自定义逻辑(如日志、性能统计、异常处理)
public void log(ProceedingJoinPoint proceedingJoinPoint){
    long begin = System.currentTimeMillis();

    try {
        proceedingJoinPoint.proceed(); // 执行目标方法
    } catch (Throwable e) {
        System.out.println("方法执行异常"+e.getMessage());
    }

    long end = System.currentTimeMillis();
    System.out.println("方法用时:" + (end - begin) + "ms");
}

逻辑解析

  1. 获取开始时间 begin
  2. 执行目标方法 proceedingJoinPoint.proceed()
  3. 捕获异常并处理
  4. 计算执行时间并打印

✅ 总结:环绕通知可以完整控制目标方法的执行流程,是最强大的 AOP 通知类型。


4. 笔记重点

  • @Aspect:声明切面类
  • @Around + execution(...):指定拦截目标方法
  • ProceedingJoinPoint.proceed():执行目标方法,环绕通知中必须调用
  • 环绕通知可以:
    • 前置逻辑(方法调用前)
    • 后置逻辑(方法调用后)
    • 异常处理
    • 性能统计、日志记录等
posted @ 2026-07-02 17:40  畅畅c  阅读(2)  评论(0)    收藏  举报