Springboot后端开发进阶
字数 2218 2025-11-08 10:04:01

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标注时:

  1. 方法开始前,Spring会开启一个数据库事务。
  2. 方法正常执行完毕,Spring会提交事务。
  3. 如果方法执行过程中抛出了运行时异常(RuntimeException)或错误(Error),Spring会回滚事务。
  4. 如果抛出的是受检异常(如IOException),默认情况下事务不会回滚。可以通过@Transactional(rollbackFor = Exception.class)来设置所有异常都回滚。

2.3 事务进阶属性

@Transactional注解还有其他重要属性:

  • rollbackFor / rollbackForClassName: 指定哪些异常类型会触发回滚。
  • noRollbackFor / noRollbackForClassName: 指定哪些异常类型不会触发回滚。
  • propagation: 定义事务的传播行为(例如,如果当前已存在事务,则如何操作)。
  • isolation: 定义事务的隔离级别。

三、文件上传功能

3.1 基础概念

文件上传是Web应用常见功能。Spring MVC提供了MultipartFile接口来简化接收上传文件的过程。

3.2 本地存储实现

a) 配置
application.ymlapplication.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开发中的三个进阶主题:

  1. 批量操作与主键返回:利用MyBatis的<foreach>@Options注解高效处理关联数据的插入。
  2. 事务管理:使用@Transactional注解确保复杂业务操作的原子性和数据一致性。
  3. 文件上传:实现了从本地存储到阿里云OSS云存储的方案,并讲解了静态资源映射。

掌握这些技术将极大提升你开发企业级SpringBoot应用的能力。建议在实际项目中结合具体需求进行练习和深化。

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 属性,用于接收前端传来的工作经历列表。 2. 数据访问层 (Mapper) EmpMapper.java : 负责插入员工数据并获取主键。 EmpExprMapper.java : 负责批量插入工作经历。 EmpExprMapper.xml : 在XML中编写批量插入的SQL。 3. 业务逻辑层 (Service) 在Service层,我们需要组织业务逻辑:先插入员工,再利用返回的员工ID去插入工作经历。 4. 控制层 (Controller) Controller层接收前端传来的JSON数据,并调用Service。 二、Spring事务管理 2.1 事务定义 事务是数据库操作的一个不可分割的工作单元。事务内的所有操作要么全部成功(提交),要么全部失败(回滚)。这确保了数据的一致性。 默认情况 :在Spring中,每个独立的数据库操作(如一条 @Insert 语句)通常会被自动提交。 2.2 Spring声明式事务管理 Spring提供了强大的声明式事务管理,通过简单的注解 @Transactional 即可实现,无需编写繁琐的事务管理代码。 使用方法 : 在需要事务管理的 Service层方法 上添加 @Transactional 注解。 工作原理 : 当方法被 @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 中设置文件上传大小限制。 b) 代码实现 c) 静态资源映射 为了让浏览器能通过返回的URL访问到本地文件,需要在配置类中设置静态资源映射。 3.3 阿里云OSS云存储实现 对于生产环境,更推荐使用对象存储服务(如阿里云OSS),因为它具备高可用、高扩展、安全可靠等优点。 a) 引入依赖 b) 配置OSS参数 c) 代码实现 总结 本文档详细讲解了SpringBoot开发中的三个进阶主题: 批量操作与主键返回 :利用MyBatis的 <foreach> 和 @Options 注解高效处理关联数据的插入。 事务管理 :使用 @Transactional 注解确保复杂业务操作的原子性和数据一致性。 文件上传 :实现了从本地存储到阿里云OSS云存储的方案,并讲解了静态资源映射。 掌握这些技术将极大提升你开发企业级SpringBoot应用的能力。建议在实际项目中结合具体需求进行练习和深化。