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/目录下新建controller、service、mapper文件夹对应各层
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
- 将
src/main/resources下的application的后缀名修改为.yml - 配置格式如下
server:
servlet:
context-path: /api #配置全局路由
port: 8080 #配置端口,默认为8080
3.快速切换全局配置
- 在
src/main/resources/目录下新增文件application-[dev自定义名字].yml(可以多创建几个用来切换) - 在新增的文件中编辑
server:
servlet:
context-path: /api #配置全局路由
port: 9090 #配置端口,默认为8080
- 在原始的
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持久层框架
- 在
pom.xml中引入驱动
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.3.0</version>
</dependency>
- 在
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→ 指定时区,避免时间错乱
- 在
DemoApplication.java里引入注解
@SpringBootApplication
@RestController
@MapperScan("com.example.demo1.mapper") //新添加的内容,括号里的地址为你建立的持久层软件包地址
2.导入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. 使用实体类
- 在
mapper软件包里创建UserMapper类 - 写入框架,在里面可以写一些增删改查功能
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> {
}
- 来到
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
- 按类型 查找 Bean
- 如果有多个同类型 Bean → 按名称匹配(字段名 / 参数名)
- 如果仍然不唯一 → 需要配合
@Qualifier或@Primary - 可选注入:
@Autowired(required = false)作用:告诉 Spring:这个依赖“能注入就注入,注不进来也别报错”。注入失败则值为null
@Resource
- 如果写了
@Resource(name="xxx")→ 强制按名称 查找 - 如果没写
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的方法已被执行");
}
}
- 在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;
}
}
- 在对应构造函数上方使用
@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());
}
}
八、设置统一返回的结果类(为方便接收,将格式统一)
- 在项目中创建
common包 - 在包下创建
Result类 - 写入以下代码
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;
}
}
九、在控制层调用这些类示例
- 在
contorller下新建UserController类 - 写入以下代码,作用是实现基本的增删改查
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 要使用的Mapper是UserMapper,而UserMapper是BaseMapper<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.添加分页插件
- 引入
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>
- 引入可选模块,引入代码也从官网复制
<!-- jdk 11+ 引入可选模块 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
- 创建
config软件包 - 在
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_time、update_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 接口,并重写 insertFill 和 updateFill 方法。
@JsonFormat、@TableField
- 按照官网提示,先创建
handler软件包 - 在该软件包下,创建与官网代码名字相同的(名字相同为了复制方便)
MyMetaObjectHandler类 - 粘贴官网代码并导入相应的包。并把原有的逻辑注释掉,写上自己的新逻辑
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:图形化页面导入
- 左上角文件>新建>模块
- 在弹出的页面里把需要的依赖都选上
- 创建模块,在
.mvn/模块名/目录下 - 进入新建的
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类返回的是数据而非页面
二十、配置初始化回调方法
在 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)方法,通知可以在目标方法执行前、后、异常时执行。
- 必须配合 Spring 容器管理(通常加上
@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?)- 分解:
*:匹配任意返回类型org.example.c4_aop.UserService.*:匹配UserService类的所有方法(..):匹配任意数量、任意类型的参数
⚡ 小结:该表达式表示 对 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");
}
逻辑解析
- 获取开始时间
begin - 执行目标方法
proceedingJoinPoint.proceed() - 捕获异常并处理
- 计算执行时间并打印
✅ 总结:环绕通知可以完整控制目标方法的执行流程,是最强大的 AOP 通知类型。
4. 笔记重点
@Aspect:声明切面类@Around+execution(...):指定拦截目标方法ProceedingJoinPoint.proceed():执行目标方法,环绕通知中必须调用- 环绕通知可以:
- 前置逻辑(方法调用前)
- 后置逻辑(方法调用后)
- 异常处理
- 性能统计、日志记录等

浙公网安备 33010602011771号