SpringBoot后端开发进阶教学文档
概述
本文档将深入讲解SpringBoot后端开发中的三个高级主题:使用MyBatis进行批量数据操作、Spring声明式事务管理以及实现本地与云存储的文件上传功能。这些是构建健壮、高效企业级应用的核心技术。
一、新增员工功能实现与批量插入
1.1 核心知识点
a) MyBatis 批量插入数据
当需要一次性向数据库插入多条记录时,使用逐条插入的方式性能极差。MyBatis提供了<foreach>标签来优雅地实现批量插入。
<foreach>标签详解:collection: 指定要遍历的集合参数,如list或数组名。item: 定义遍历过程中每个元素的别名。separator: 定义每次循环结束后的分隔符,在批量插入中通常为逗号,,用于分隔多个values子句。
b) 获取插入数据的主键ID
在插入数据后,经常需要立即使用该数据生成的主键ID(例如,用于关联插入其他表)。MyBatis通过@Options注解可以方便地实现这一点。
@Options注解详解:useGeneratedKeys = true: 告知MyBatis使用JDBC的getGeneratedKeys方法来获取数据库内部生成的主键。keyProperty: 指定一个Java对象属性,用于接收生成的主键值。
1.2 具体实现步骤
1. 数据模型 (Entity)
假设有两个实体类:Emp(员工)和EmpExpr(工作经历)。Emp中包含一个List<EmpExpr> exprList属性,用于接收前端传来的工作经历列表。
// Emp 员工实体
public class Emp {
private Integer id; // 主键ID,由数据库自动生成
private String username;
private String name;
// ... 其他字段
private LocalDateTime createTime;
private LocalDateTime updateTime;
private List<EmpExpr> exprList; // 工作经历列表
// getter/setter...
}
// EmpExpr 工作经历实体
public class EmpExpr {
private Integer id;
private Integer empId; // 关联的员工ID
private Date begin;
private Date end;
private String company;
private String job;
// getter/setter...
}
2. 数据访问层 (Mapper)
EmpMapper.java: 负责插入员工数据并获取主键。
@Mapper
public interface EmpMapper {
/**
* 新增员工
* @Options 注解确保插入后,传入的emp对象的id属性会被自动赋值为数据库生成的主键
*/
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("INSERT INTO emp(username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time) " +
"VALUES (#{username}, #{name}, #{gender}, #{phone}, #{job}, #{salary}, #{image}, #{entryDate}, #{deptId}, #{createTime}, #{updateTime})")
void insert(Emp emp);
}
EmpExprMapper.java: 负责批量插入工作经历。
@Mapper
public interface EmpExprMapper {
/**
* 批量插入工作经历
* @param list 工作经历列表
*/
void insertBatch(List<EmpExpr> list);
}
EmpExprMapper.xml: 在XML中编写批量插入的SQL。
<?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.yourpackage.mapper.EmpExprMapper">
<insert id="insertBatch">
INSERT INTO emp_expr (emp_id, begin, end, company, job) VALUES
<foreach collection="list" item="empExpr" separator=",">
(#{empExpr.empId}, #{empExpr.begin}, #{empExpr.end}, #{empExpr.company}, #{empExpr.job})
</foreach>
</insert>
</mapper>
3. 业务逻辑层 (Service)
在Service层,我们需要组织业务逻辑:先插入员工,再利用返回的员工ID去插入工作经历。
@Service
@Slf4j
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Autowired
private EmpExprMapper empExprMapper;
@Override
public void save(Emp emp) {
// 1. 设置创建和更新时间
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
// 2. 插入员工数据。插入后,emp.getId() 会自动获得数据库生成的主键值
empMapper.insert(emp);
// 3. 处理工作经历列表
List<EmpExpr> exprList = emp.getExprList();
if (exprList != null && !exprList.isEmpty()) {
// 为每一条工作经历设置其所属员工的ID
exprList.forEach(item -> item.setEmpId(emp.getId()));
// 4. 批量插入工作经历
empExprMapper.insertBatch(exprList);
}
log.info("新增员工成功,员工ID: {}", emp.getId());
}
}
4. 控制层 (Controller)
Controller层接收前端传来的JSON数据,并调用Service。
@RestController
@RequestMapping("/emps")
@Slf4j
public class EmpController {
@Autowired
private EmpService empService;
@PostMapping
public Result save(@RequestBody Emp emp) { // @RequestBody 注解用于接收JSON格式的请求体
log.info("新增员工, 参数: {}", emp);
empService.save(emp);
return Result.success(); // 返回统一响应结果
}
}
二、Spring事务管理
2.1 事务定义
事务是数据库操作的一个不可分割的工作单元。事务内的所有操作要么全部成功(提交),要么全部失败(回滚)。这确保了数据的一致性。
默认情况:在Spring中,每个独立的数据库操作(如一条
@Insert语句)通常会被自动提交。
2.2 Spring声明式事务管理
Spring提供了强大的声明式事务管理,通过简单的注解@Transactional即可实现,无需编写繁琐的事务管理代码。
使用方法:
在需要事务管理的Service层方法上添加@Transactional注解。
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Autowired
private EmpExprMapper empExprMapper;
@Transactional // 添加此注解,声明该方法需要在一个事务中运行
@Override
public void save(Emp emp) {
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
empMapper.insert(emp);
List<EmpExpr> exprList = emp.getExprList();
if (exprList != null && !exprList.isEmpty()) {
exprList.forEach(item -> item.setEmpId(emp.getId()));
empExprMapper.insertBatch(exprList);
}
// 模拟一个可能出现的异常
// int i = 1 / 0;
// 如果打开此注释,执行到此处会抛出异常,事务会回滚,员工和其工作经历都不会被插入数据库
}
}
工作原理:
当方法被@Transactional标注时:
- 方法开始前,Spring会开启一个数据库事务。
- 方法正常执行完毕,Spring会提交事务。
- 如果方法执行过程中抛出了运行时异常(
RuntimeException)或错误(Error),Spring会回滚事务。 - 如果抛出的是受检异常(如
IOException),默认情况下事务不会回滚。可以通过@Transactional(rollbackFor = Exception.class)来设置所有异常都回滚。
2.3 事务进阶属性
@Transactional注解还有其他重要属性:
rollbackFor / rollbackForClassName: 指定哪些异常类型会触发回滚。noRollbackFor / noRollbackForClassName: 指定哪些异常类型不会触发回滚。propagation: 定义事务的传播行为(例如,如果当前已存在事务,则如何操作)。isolation: 定义事务的隔离级别。
三、文件上传功能
3.1 基础概念
文件上传是Web应用常见功能。Spring MVC提供了MultipartFile接口来简化接收上传文件的过程。
3.2 本地存储实现
a) 配置
在application.yml或application.properties中设置文件上传大小限制。
spring:
servlet:
multipart:
max-file-size: 10MB # 单个文件最大大小
max-request-size: 100MB # 整个请求的最大大小
b) 代码实现
@RestController
@RequestMapping("/upload")
public class FileUploadController {
/**
* 接收单个文件上传
* @param file 使用 @RequestParam("file") 接收前端传来的文件
* @return 文件的访问路径
*/
@PostMapping("/local")
public Result<String> uploadLocal(@RequestParam("file") MultipartFile file) throws IOException {
// 1. 校验文件是否为空
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
// 2. 生成唯一文件名,防止覆盖
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = UUID.randomUUID().toString() + fileExtension;
// 3. 指定文件存储目录
String uploadDir = "/path/to/your/upload/directory/";
File destFile = new File(uploadDir + newFileName);
// 4. 确保目录存在
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
// 5. 将文件传输到目标位置
file.transferTo(destFile);
// 6. 返回文件的访问URL(需要配置静态资源映射才能通过此URL访问)
return Result.success("/uploads/" + newFileName);
}
}
c) 静态资源映射
为了让浏览器能通过返回的URL访问到本地文件,需要在配置类中设置静态资源映射。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 将 /uploads/** 的请求映射到本地文件系统的 /path/to/your/upload/directory/ 目录
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:/path/to/your/upload/directory/");
}
}
3.3 阿里云OSS云存储实现
对于生产环境,更推荐使用对象存储服务(如阿里云OSS),因为它具备高可用、高扩展、安全可靠等优点。
a) 引入依赖
<!-- 阿里云OSS SDK -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
b) 配置OSS参数
# application.yml
aliyun:
oss:
endpoint: oss-cn-beijing.aliyuncs.com # 你的OSS endpoint
accessKeyId: your-access-key-id
accessKeySecret: your-access-key-secret
bucketName: your-bucket-name
c) 代码实现
@Service
public class OssService {
@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) {
// 1. 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = "images/" + UUID.randomUUID().toString() + fileExtension;
// 2. 创建OSSClient实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 3. 上传文件流到OSS
ossClient.putObject(bucketName, objectName, file.getInputStream());
// 4. 构造文件的公开访问URL
String fileUrl = "https://" + bucketName + "." + endpoint + "/" + objectName;
return fileUrl;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
// 在Controller中调用
@RestController
@RequestMapping("/upload")
public class FileUploadController {
@Autowired
private OssService ossService;
@PostMapping("/oss")
public Result<String> uploadOss(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
String url = ossService.upload(file);
if (url != null) {
return Result.success(url);
} else {
return Result.error("文件上传失败");
}
}
}
总结
本文档详细讲解了SpringBoot开发中的三个进阶主题:
- 批量操作与主键返回:利用MyBatis的
<foreach>和@Options注解高效处理关联数据的插入。 - 事务管理:使用
@Transactional注解确保复杂业务操作的原子性和数据一致性。 - 文件上传:实现了从本地存储到阿里云OSS云存储的方案,并讲解了静态资源映射。
掌握这些技术将极大提升你开发企业级SpringBoot应用的能力。建议在实际项目中结合具体需求进行练习和深化。