Spring Boot整合MyBatis与文件上传:从分层架构到文件上传下载实战

第一部分:Spring Boot兼容Servlet

1.1 Servlet能力兼容

⭐ 老师强调 :框架兼容原有Servlet形式接收参数,之前提到的Servlet相关能力都支持。

请求转发与重定向:

方式 实现 特点
请求转发 return "forward:地址" 服务端内部跳转,浏览器地址不变
重定向 return "redirect:地址" 客户端跳转,浏览器重新发请求,地址变

⭐ 老师强调 :请求转发用forward:,地址可指前端页面或后端方法;重定向用redirect:,会让浏览器重新请求,方法重执行。

复制代码
@Controller
public class HelloController {
    
    // 请求转发到test3方法
    @RequestMapping("test2")
    public String test2() {
        return "forward:test3";
    }
    
    // 重定向到test3方法
    @RequestMapping("test3")
    public String test3() {
        return "redirect:test3";
    }
}

1.2 返回数据 vs 返回页面

注解 返回内容
不加@ResponseBody 走视图解析,找页面
加@ResponseBody 直接返回数据

⭐ 老师强调:

  • 返回普通文本时直接返回即可,信息进到success的value里
  • 返回json数据必须加特定语句(设置produces),否则只是渲染页面而非返回数据
  • 加了模板后普通文本也得经后端,不能直接取页面

第二部分:Spring Boot返回数据

2.1 返回普通文本

复制代码
@RequestMapping("test1")
@ResponseBody
public String test1() {
    return "Hello";  // 返回普通文本
}

2.2 返回JSON数据

复制代码
@RequestMapping(value = "test1", produces = "application/json;charset=UTF-8")
@ResponseBody
public String test1() {
    return "{\"message\":\"hello\"}";
}

2.3 返回对象

复制代码
@RequestMapping("test1")
@ResponseBody
public User test1() {
    User user = new User();
    user.setName("张三");
    user.setAge(20);
    // ...
    return user;
}

⭐ 老师强调 :Spring Boot中可返回任意格式数据(string、int、double、对象等),Java程序内直接换格式即可,无需手动拼接。这是相比Servlet只能返回字符串的优势。

2.4 返回集合

复制代码
@RequestMapping("test1")
@ResponseBody
public List<User> test1() {
    List<User> users = new ArrayList<>();
    users.add(user1);
    users.add(user2);
    return users;  // 返回集合,前端解析后变成数组
}

⭐ 老师强调 :返回集合和返回别的结构都常用,但集合转数组后必须遍历处理,不能盲目直取。

2.5 数据查询返回

⭐ 老师强调:

  • 按id查找通常返回唯一一条信息
  • 查询也可以返回集合,用来获取多条数据
  • 之前Servlet封装了统一响应包(含message、data、code),数据都在data里
  • 若只返回一个对象,直接从data取对象用即可,不必循环

第三部分:Spring Boot整合MyBatis

3.1 引入依赖

复制代码
<dependencies>
    <!-- springboot核心基础启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- 加载web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- thymeleaf模板 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.2</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
</dependencies>

⭐ 老师强调:

  • 先引入两个依赖:MyBatis starter 和 MySQL驱动,少一个都无法连库
  • Spring Boot 2 配 JDK 8,Spring Boot 3 至少要求 Java 17

3.2 配置文件(application.yml)

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/qcby07?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 2020
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    cache: false #关闭页面缓存
    encoding: utf-8 #编码
    prefix: classpath:/templates/ #页面映射路径
    suffix: .html #视图的后缀
    mode: HTML5 #模板模式
  servlet:
    multipart:
      # 单个文件最大上传大小
      max-file-size: 10MB
      # 每次请求上传文件大小最大值
      max-request-size: 100MB

mybatis:
  mapper-locations: classpath:mapper/*.xml   #对应mapper映射xml文件所在路径
  type-aliases-package: com.qcby.entity      #对应实体类路径
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

⭐ 老师强调:

  • datasource和thymeleaf都挂在spring下,缩进对齐
  • url、username、password是datasource的子项,进一步缩进
  • mybatis和spring是并列关系,都得顶格写
  • mapper-locations指定xml映射文件位置,对应放到resource下mapper目录
  • 文件上传需配spring.servlet.multipart,否则无法支持上传

3.3 主启动类加@MapperScan

复制代码
@SpringBootApplication
@MapperScan("com.qcby.dao")  // 扫描dao层包
public class SpringBootProjectApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootProjectApplication.class, args);
    }
}

⭐ 老师强调 :加@MapperScan注解,用于扫描com.qcby.dao下的dao层包,这样框架才能识别写在该包里的MyBatis接口及其实现。注解扫描路径必须对准dao层,否则接口无法被识别。

3.4 分层调用链

复制代码
前端请求 → Controller → Service → Dao(MyBatis) → 数据库
    ↑                                                │
    └──────────────────返回数据────────────────────┘
层 注解 职责
Controller @Controller 接收请求、返回数据
Service @Service 处理业务逻辑
Dao @Mapper(通过@MapperScan) 数据访问

⭐ 老师强调:

  • 简单项目Controller可直接调Dao层,但为规范与扩展起见,最好保留Service层
  • 接口的不同实现类相互不干扰,各自独立
  • 通常建service包,其下再建service.impl包放实现类
  • 类名约定为接口名后加Impl,这只是习惯而非语法强制

第四部分:完整分层代码

4.1 实体类(Student.java)

复制代码
package com.qcby.entity;

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String sex;
    private Integer tid;

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", tid=" + tid +
                '}';
    }

    // getter/setter省略
}

4.2 Dao层(StudentDao.java)

复制代码
package com.qcby.dao;

import com.qcby.entity.Student;
import java.util.List;

public interface StudentDao {
    public List<Student> findAll();
    public int delete(Integer id);
}

4.3 映射文件(StudentDao.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.qcby.dao.StudentDao">

    <select id="findAll" resultType="com.qcby.entity.Student">
        select * from student
    </select>
    <delete id="delete" parameterType="java.lang.Integer">
        delete from student where id = #{id}
    </delete>
</mapper>

⭐ 老师强调 :此用法与纯MyBatis无区别 ,无需像原生那样创建SqlSessionFactory或SqlSession。

4.4 Service接口(StudentService.java)

复制代码
package com.qcby.service;

import com.qcby.entity.Student;
import java.util.List;

public interface StudentService {
    public List<Student> findAll();
    public int delete(Integer id);
}

4.5 Service实现类(StudentServiceImpl.java)

复制代码
package com.qcby.service.serviceImpl;

import com.qcby.dao.StudentDao;
import com.qcby.entity.Student;
import com.qcby.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentServiceImpl implements StudentService {
    
    @Autowired
    private StudentDao studentDao;

    @Override
    public List<Student> findAll() {
        System.out.println("service查找");
        List<Student> list = studentDao.findAll();
        return list;
    }

    @Override
    public int delete(Integer id) {
        int code = studentDao.delete(id);
        return code;
    }
}

⭐ 老师强调:

  • @Service注解把创建对象权利交给Spring并标明层级
  • @Autowired让框架自动扫描并给属性赋值 ,本质是找到实现类、创建对象再注入,省去手动new
  • 调studentDao方法返回的是一个集合

4.6 Controller层(StudentController.java)

复制代码
package com.qcby.controller;

import com.qcby.entity.Student;
import com.qcby.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
public class StudentController {

    @Autowired
    StudentService studentService;

    @RequestMapping("search")
    @ResponseBody
    public List<Student> findAll() {
        System.out.println("controller层查找");
        List<Student> list = studentService.findAll();
        return list;
    }

    @RequestMapping("delete")
    @ResponseBody
    public int delete(Integer id) {
        int code = studentService.delete(id);
        return code;
    }
}

⭐ 老师强调:

  • Controller通过@RequestMapping映射请求路径(如search、delete)
  • 必须加@ResponseBody,否则会走视图引擎去找页面而非返回数据
  • 后续可加method限制只允许post等

第五部分:文件上传

5.1 前端写法

复制代码
<form class="filebox">
    选择文件:<input type="file" class="file" name="file">
    <input type="button" class="btn" value="上传">
</form>

<script>
    $(".btn").click(function(){
        // 获取form表单信息,序列化
        var formdata = new FormData($(".filebox")[0]);
        
        if(formdata.get("file").name){  // 判断是否选中文件
            $.ajax({
                url:"/upload",
                type:"post",
                data:formdata,  // data是对象,直接传,不用加{}
                contentType:false,  // 设置前端给后端的数据格式为非文本
                processData:false,  // 不把数据转换为字符串
                success:function(value){
                    console.log(value);
                }
            })
        } else {
            alert("请选择文件");
        }
    })
</script>

⭐ 老师强调:

  • 文件上传只能是post请求,用get不行
  • contentType:false设置数据格式为非文本
  • processData:false不把数据转换为字符串形式
  • 前端拦截:只有选中文件才允许上传,没选则禁止

5.2 后端配置

复制代码
spring:
  servlet:
    multipart:
      max-file-size: 10MB      # 单个文件最大上传大小
      max-request-size: 100MB  # 每次请求上传文件大小最大值

⭐ 老师强调 :文件上传需配置spring.servlet.multipart,否则无法支持上传。配置位置精确到spring.servlet下,漏写会导致上传失效。

5.3 后端上传代码

复制代码
package com.qcby.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

@Controller
public class UploadController {
    // 文件存储绝对路径,Windows;Linux请改为 "/home/upload/"
    private static final String SAVE_BASE_PATH = "E:/upload/";

    /* 文件上传 */
    @RequestMapping(value = "/upload", method = RequestMethod.POST, 
                    produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file, 
                         HttpServletRequest request) throws IOException {

        // 判断是否未选择文件
        if (file.isEmpty()) {
            return "{\"data\":\"fail\",\"msg\":\"请选择文件\"}";
        }

        // 创建文件夹对象
        File folder = new File(SAVE_BASE_PATH);
        if (!folder.exists()) {
            folder.mkdirs();
        }

        // 原始名称
        String originalFileName = file.getOriginalFilename();
        // 提取后缀,UUID生成新文件名,防止同名覆盖
        String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
        String newFileName = UUID.randomUUID().toString().replace("-", "") + suffix;

        // 构造目标文件
        File newFile = new File(folder, newFileName);

        System.out.println("文件保存路径:" + newFile.getAbsolutePath());
        // 将内存中的数据写入磁盘
        file.transferTo(newFile);

        // 返回JSON字符串
        return "{\"data\":\"success\",\"saveFileName\":\"" + newFileName 
             + "\",\"originalFileName\":\"" + originalFileName + "\"}";
    }
}

⭐ 老师强调:

  • 后端接收参数名必须和前端form表单里input的name一致
  • file变量里装的就是传来的文件
  • 要先判断文件是否为空
  • transferTo方法用来把文件写入指定磁盘目录
  • 重命名用lastIndexOf找最后一个点的位置,substring裁剪出后缀
  • UUID原带杠需replace为空,且拼接时要手动加点,否则后缀丢失
  • Spring Boot内置Tomcat,需自定义目录存储文件(如E盘upload)

第六部分:文件下载

6.1 前端写法

复制代码
<h3>文件下载</h3>
<div class="block">
    <div>服务器保存文件名(saveFileName):</div>
    <input type="text" class="downFileName" placeholder="上传成功后复制saveFileName填入">
    <button class="内部工具Btn">下载文件</button>
</div>

<script>
    $(".内部工具Btn").on("click", function () {
        var fileNameVal = $(".downFileName").val().trim();
        if (!fileNameVal) {
            alert("请输入服务器保存的文件名!");
            return;
        }
        // 跳转GET请求触发下载
        location.href = "/内部工具?filename=" + fileNameVal;
    });
</script>

⭐ 老师强调 :发起下载请求不一定要用ajax,用location.href写个带参地址也能发get请求。

6.2 后端下载代码

复制代码
@RequestMapping("/内部工具")
public void 内部工具(HttpServletRequest request, HttpServletResponse response) 
        throws IOException {
    String filename = request.getParameter("filename");
    File file = new File(SAVE_BASE_PATH, filename);

    // 判断文件是否存在
    if (!file.exists()) {
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("文件不存在");
        return;
    }

    // 解决中文文件名乱码
    String 内部工具FileName = URLEncoder.encode(filename, "UTF-8");
    response.setHeader("Content-Disposition", "attachment;filename=" + 内部工具FileName);
    response.setContentType("application/octet-stream");

    // try-with-resources 自动关闭流
    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
         ServletOutputStream out = response.getOutputStream()) {

        byte[] buffer = new byte[1024];
        int len;
        while ((len = bis.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        out.flush();
    }
}

⭐ 老师强调:

  • 下载本质是从磁盘位置把文件以二进制字节流形式发给浏览器
  • 浏览器识别后自动触发下载机制
  • 文件不存在就返回"文件不存在"
  • 存在则处理乱码,并设置响应格式为二进制字节流返回资源
  • 未来文件信息从数据库获取,添加时存进去,下载时查出

附录一:核心对比速查表

返回数据方式

返回类型 注解 说明
普通文本 @ResponseBody 直接返回
JSON @ResponseBody + produces 设置响应格式
对象 @ResponseBody 框架自动转json
集合 @ResponseBody 前端解析成数组

请求跳转

方式 写法 特点
请求转发 return "forward:地址" 服务端内部跳转,地址不变
重定向 return "redirect:地址" 客户端跳转,地址变

文件上传配置

配置项 说明
max-file-size 单个文件最大上传大小
max-request-size 每次请求上传文件大小最大值

文件上传前端参数

参数 值 说明
contentType false 数据格式为非文本
processData false 不把数据转为字符串
相关推荐
计算机源码社4 小时前
【27届大数据毕设】基于Spark的TikTok短视频属性分享与互动比率可视化分析系统 基于机器学习的TikTok短视频认证属性与互动效能关联分析
大数据·hadoop·python·机器学习·spark·毕业设计·课程设计
ha_lydms13 小时前
MaxCompute中加密与解密函数
大数据·数据库·数据仓库·hadoop·dataworks·maxcompute·odps
Q26433650231 天前
【有源码】基于Spark的电商客户细分与盈利洞察分析系统-面向精准营销的电商客户细分模型构建与盈利能力可视化研究
大数据·hadoop·分布式·数据挖掘·数据分析·spark·毕业设计
计算机源码社1 天前
【27届大数据毕设】基于Spark的AI艺术创作者价值评估与市场趋势预测研究 基于Python与Spark的AI生成艺术受众画像及异常热度识别可视化平台
大数据·人工智能·hadoop·python·spark·毕业设计·课程设计
千桐科技2 天前
数据中台 ETL 编排实践:基于 qData 开源版实现数据接入、转换与调度执行
数据仓库·开源·etl
ha_lydms2 天前
MaxCompute中窗口函数
大数据·hadoop·阿里云·云计算·dataworks·maxcompute·odps
千里码aicood2 天前
基于Hadoop的机票价格波动分析系统的设计与实现
大数据·hadoop·分布式
IT毕设梦工厂2 天前
计算机毕业设计选题推荐:基于大数据的供应链数据分析与可视化|毕业设计选题|计算机毕设|选题推荐|毕设指导|项目定制|源码|高质量项目
大数据·hive·hadoop·python·数据分析·spark·课程设计
BYSJMG2 天前
计算机毕业设计选题推荐|【基于深度学习的面部关键特征识别与检测】YOLO目标检测+ONNX推理
大数据·hadoop·数据分析·spark·课程设计