外观
SpringBoot-1 学习笔记
前后端分离式开发的流程,以及较简单代码的逻辑实现,能够阅读接口文档实现项目代码的开发。本文按个人学习路径整理复习要点,熟练度 4/5,侧重快速回顾,忽略细节铺垫。
1. 学习接收前端的参数
- 四种接收姿势与注解一一对应:查询参数
?name=xx→@RequestParam;路径参数/user/{id}→@PathVariable;JSON 请求体 →@RequestBody;表单参数可直接用实体对象接收。 @RequestParam默认必传,可选参数要写required = false;前端参数名与形参不一致时用@RequestParam("name")显式指定。@PathVariable("id")要跟@RequestMapping里的{id}占位符一一对应。- 一个方法只能有一个
@RequestBody;GET 请求不能带请求体,POST 传 JSON 时必须设Content-Type: application/json。 - 前端传的日期字符串需要
@DateTimeFormat(pattern = "yyyy-MM-dd")才能绑定到 Date 字段。
java
// 查询参数:/user?name=zs&age=20
@GetMapping("/user")
public Result getByName(@RequestParam String name,
@RequestParam(required = false) Integer age) {
return Result.success(userService.getByName(name, age));
}
// 路径参数:/user/1
@GetMapping("/user/{id}")
public Result getById(@PathVariable Integer id) {
return Result.success(userService.getById(id));
}
// JSON 请求体
@PostMapping("/user")
public Result add(@RequestBody User user) {
return Result.success(userService.add(user));
}2. 学习简单的开发流程,分层开发
- 标准三层:Controller(接收参数、响应结果)→ Service(业务逻辑、事务)→ Mapper(数据访问)。Controller 不写业务,Service 不直接拼 SQL。
- Service 习惯"接口 + 实现类",实现类加
@Service;Mapper 接口加@Mapper(或在启动类加@MapperScan统一扫包)。 - 注入推荐构造器注入(
final字段)利于测试;简单项目用@Autowired字段注入也可。 - Lombok 减少样板代码:
@Data生成 getter/setter/toString,@Slf4j提供log对象。 - 统一返回
Result{code, message, data}封装,前端按 code 判断成功失败,接口只返回数据语义。 - 易错点:Controller 直接调 Mapper 跳过 Service;实现类忘加
@Service导致启动报"找不到 Bean"。
java
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
return Result.success(empService.getById(id));
}
}
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public Emp getById(Integer id) {
return empMapper.findById(id);
}
}3. 学习Mybatis
- 半自动 ORM:SQL 自己写,结果映射交给框架。接口方法 + 注解即可工作,
@Mapper让 Spring 生成代理实现。 - 简单 SQL 用注解
@Select/@Insert/@Update/@Delete;复杂或动态 SQL 改用 XML。 #{}是预编译占位符,安全防 SQL 注入;${}直接字符串拼接,仅可用于表名/列名等无法占位处,有注入风险慎用。- 多参数方法必须用
@Param("xx")命名参数,否则#{}取不到值。 - 实体字段驼峰与数据库列下划线不对应时,开启驼峰映射
map-underscore-to-camel-case: true,否则属性全为 null。 - 易错点:接口忘加
@Mapper;返回值类型与 SQL 结果不匹配;#{}里写错参数名。
java
@Mapper
public interface EmpMapper {
@Select("SELECT * FROM emp WHERE id = #{id}")
Emp findById(Integer id);
@Insert("INSERT INTO emp(name, dept_id) VALUES(#{name}, #{deptId})")
int insert(Emp emp);
}yaml
# application.yml
mybatis:
configuration:
map-underscore-to-camel-case: true4. 学习xml文件配置
- XML 映射文件用于复杂与动态 SQL;
namespace必须等于 Mapper 接口全限定名,id等于方法名,写错启动即报错。 - 动态 SQL 三件套:
<where>+<if>拼查询条件(自动处理多余的 AND)、<set>拼更新、<foreach>批量操作。 <if test="name != null and name != ''">里写的是参数名,不要带#{};字符串判空要同时判断!= ''。- 特殊字符
<>需转义或用<![CDATA[ ... ]]>;LIKE 模糊查询用CONCAT('%', #{name}, '%')防注入。 - 用
mybatis.mapper-locations: classpath:mapper/*.xml指定 XML 位置,接口与 XML 默认要求同名同包。 resultMap解决字段名不一致,以及一对一association、一对多collection关联映射。
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.example.mapper.EmpMapper">
<select id="listByCondition" resultType="com.example.pojo.Emp">
SELECT * FROM emp
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="deptId != null">
AND dept_id = #{deptId}
</if>
</where>
</select>
<delete id="deleteBatch">
DELETE FROM emp WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>yaml
mybatis:
mapper-locations: classpath:mapper/*.xml5. 学习了Maven后端代码管理
- Maven 是项目构建与依赖管理工具,一切写在
pom.xml:坐标(groupId/artifactId/version)+ 依赖 + 插件,坐标唯一标识一个依赖。 - 依赖引入
spring-boot-starter-*会自动带出传递依赖;scope控制生效范围(compile/test/provided);Spring Boot 父工程统一锁定依赖版本。 - 生命周期:clean → compile → test → package → install;
install把构件装进本地仓库供其它模块引用,跳过测试用-DskipTests。 - 仓库链路:本地
~/.m2缓存 → 中央/私服仓库;国内在settings.xml配阿里云镜像加速下载。 - 多模块工程:父 pom 用
<modules>聚合、<dependencyManagement>统一版本,子模块用<parent>继承。 - 易错点:依赖下载失败先查镜像与网络;打包缺依赖多为 scope 配错;依赖版本冲突用
exclusions排除传递依赖。
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>xml
<!-- settings.xml:国内镜像加速 -->
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>6. 学习Restful开发规范
- REST 风格:URL 只表示资源(用名词、复数),HTTP 方法表示动作——GET 查、POST 增、PUT 改、DELETE 删。
- 具体资源用路径参数定位:
GET /users/1查单个;集合操作用GET /users,不要在 URL 里写动词(如 getUserList)。 - 状态码语义化:200 成功、201 创建成功、404 资源不存在、500 服务端错误,前端靠它快速定位问题。
- 前后端分离下统一 JSON 交互 +
Result封装;接口路径习惯加/api前缀,如/api/users。 - 易错点:URL 出现动词、用 GET 做删除、修改不区分 PUT/POST、
@PathVariable忘写与占位符匹配。
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping // GET /api/users 列表
public Result list() { ... }
@GetMapping("/{id}") // GET /api/users/1 单个
public Result get(@PathVariable Long id) { ... }
@PostMapping // POST /api/users 新增
public Result save(@RequestBody User user) { ... }
@PutMapping("/{id}") // PUT /api/users/1 修改
public Result update(@PathVariable Long id, @RequestBody User user) { ... }
@DeleteMapping("/{id}") // DELETE /api/users/1 删除
public Result delete(@PathVariable Long id) { ... }
}7. 学习LogBack日志技术
- Spring Boot 默认集成 Logback;级别由低到高 trace < debug < info < warn < error,只输出不低于配置级别的日志。
- Lombok
@Slf4j直接获得log对象,用{}占位符传参,避免字符串拼接(性能更好、可读性更好)。 - 配置:
logging.level.包名=debug控制级别(想看 MyBatis SQL 就调 debug);logging.file.name输出到文件。 - 生产常用
logback-spring.xml:ConsoleAppender 控制台 + RollingFileAppender 按天/大小滚动归档,自定 pattern。 - 关键操作(登录、增删改)打 info,异常打
log.error(..., e)带堆栈,不打印密码/token 等敏感信息。 - 易错点:级别配太高看不到想要的日志;文件名写
logback.xml则无法使用 Spring profile 扩展,要用logback-spring.xml。
java
@Slf4j
@Service
public class UserService {
public User getById(Integer id) {
log.info("查询用户, id={}", id);
try {
return userMapper.findById(id);
} catch (Exception e) {
log.error("查询用户失败, id={}", id, e);
throw e;
}
}
}xml
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/springboot.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/springboot.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>8. 学习PageHelper分页插件
- PageHelper 是 MyBatis 分页插件,通过拦截器自动在 SQL 后拼
LIMIT,无需手写分页 SQL。 - 用法:
PageHelper.startPage(pageNum, pageSize)后【紧接着】第一条查询语句才生效,中间不能插其它 SQL。 - 结果用
PageInfo包装或强转Page<T>,取total(总条数)、pages(总页数)、list(当前页数据)。 reasonable: true时页码越界自动修正回第一/最后一页,防止超大页码拖垮数据库。- 入参校验:pageNum/pageSize 为空或 ≤0 时给默认值(如 1/10)。
- 易错点:startPage 后执行了多条 SQL 导致分页错乱;与 Druid 等其它拦截器顺序冲突;Spring Boot 3 注意 MyBatis 版本兼容。
java
// 必须紧跟查询语句
PageHelper.startPage(pageNum, pageSize);
List<Emp> list = empMapper.listByCondition(emp);
PageInfo<Emp> pageInfo = new PageInfo<>(list);
// pageInfo.getTotal(); pageInfo.getPages(); pageInfo.getList();java
// 或者直接强转 Page
Page<Emp> page = (Page<Emp>) empMapper.listByCondition(emp);
page.getTotal();
page.getPages();yaml
pagehelper:
helper-dialect: mysql
reasonable: true9. Spring事务管理
- 事务保证一组写操作"要么全部成功、要么全部回滚",满足 ACID;Spring 用
@Transactional声明式管理,免去手动开启/提交/回滚。 - 注解加在 Service 实现类的【方法】上最合适,并配
rollbackFor = Exception.class,让所有异常(含 checked 异常)都回滚。 - 默认只回滚 RuntimeException;事务失效三坑:异常被 try-catch 吞掉、方法非 public、同类内部
this.xxx()调用(不走代理)。 - 传播行为
propagation:REQUIRED(默认,加入当前事务)、REQUIRES_NEW(另开新事务,常用于记账等独立操作)。 - 纯查操作不用加事务;涉及多表写入(转账、下单)必须包在事务里,且事务方法要短,避免长事务占用连接。
java
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void transfer(Integer fromId, Integer toId, BigDecimal amount) {
accountMapper.deduct(fromId, amount); // 扣款
// 若这里抛出异常,上面的扣款自动回滚
accountMapper.add(toId, amount); // 加款
}
}10. 阿里云OSS存储
- OSS 是对象存储服务:文件传到云端,数据库只存访问 URL,减轻服务器磁盘与带宽压力。
- 核心概念:Bucket(存储空间)、Endpoint(地域节点)、AccessKeyId/AccessKeySecret(身份凭证)、objectName(对象键)。
- 上传三步:
new OSSClientBuilder().build(endpoint, ak, sk)→putObject(bucket, objectName, inputStream)→shutdown()。 - 文件名用 UUID + 原文件名重命名,避免同名覆盖;用日期做目录前缀分层,如
upload/2026/09/xxx.jpg。 - 凭证通过
@Value注入或环境变量读取,绝不能硬编码进代码;AccessKey 泄露会被刷流量,务必保密。 - 易错点:Endpoint 与 Bucket 地域不一致会报错;Bucket 默认私有,需签名 URL(generatePresignedUrl)才能访问;大文件上传用服务端直传或 STS 临时凭证;删除用
ossClient.deleteObject。
java
@Value("${aliyun.oss.endpoint}") private String endpoint;
@Value("${aliyun.oss.accessKeyId}") private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}") private String accessKeySecret;
@Value("${aliyun.oss.bucketName}") private String bucketName;
public String upload(MultipartFile file) throws IOException {
OSS ossClient = new OSSClientBuilder()
.build(endpoint, accessKeyId, accessKeySecret);
String objectName = "upload/" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM")) + "/"
+ UUID.randomUUID().toString().replace("-", "")
+ "-" + file.getOriginalFilename();
ossClient.putObject(bucketName, objectName, file.getInputStream());
ossClient.shutdown();
return "https://" + bucketName + "." + endpoint + "/" + objectName;
}yaml
aliyun:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
bucket-name: your-bucket-name