news 2026/9/30 8:24:39

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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每次请求上传文件大小最大值

文件上传前端参数

参数值说明
contentTypefalse数据格式为非文本
processDatafalse不把数据转为字符串
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 8:24:27

Java旅游网站实战:JSP+Servlet+MySQL完整工程落地指南

简介&#xff1a;本资源是一份面向计算机专业本科生及Java Web初学者的毕业设计类实践文档&#xff0c;聚焦旅游行业信息化需求&#xff0c;提供基于Java技术栈的旅游信息网站完整设计方案。文档系统阐述了采用Java开发、MySQL数据库存储、Tomcat服务器部署、Eclipse平台开发的…

作者头像 李华
网站建设 2026/9/30 8:24:25

网络热度传播机制与声量监控技术解析

我无法基于当前输入生成符合要求的博文。原因在于&#xff1a;您提供的输入内容中&#xff0c;项目标题仅为“buzz”一词&#xff0c;且后续所有字段&#xff08;项目正文、关键词、摘要描述&#xff09;均为空或未提供有效信息。根据我的角色设定与创作原则&#xff0c;我必须…

作者头像 李华
网站建设 2026/9/30 8:23:54

Creo综合建模与3D打印机械臂全流程实战:从环境配置到打印落地

从拿到一个"3D打印机械臂毕业设计"的题目&#xff0c;到用Creo从零开始把整个项目做出来&#xff0c;这个过程踩过的坑、绕过的弯&#xff0c;比我想象中多得多。很多人在学校都学过Creo的基本操作&#xff0c;但真正遇到"综合建模3D打印"这种完整工作流的…

作者头像 李华
网站建设 2026/9/30 8:23:50

Windows Server 2008域渗透:黄金票据伪造实战与检测防御

在域渗透测试里&#xff0c;黄金票据就像是一把万能钥匙——只要拿到了krbtgt账户的哈希&#xff0c;你就能为任意用户伪造一张永不过期的访问凭证。我第一次在Windows Server 2008域环境中实操黄金票据时&#xff0c;踩了不少坑&#xff0c;所以这篇进阶版实战笔记&#xff0c…

作者头像 李华
网站建设 2026/9/30 8:23:17

AI工程契约:从数据到运维的可验证责任体系

1. 这不是“搭积木”&#xff0c;而是重新理解AI工程的底层逻辑很多人看到“AI Engineering from Scratch”第一反应是&#xff1a;又要手写Transformer&#xff1f;又要从零实现反向传播&#xff1f;不是。真正的“from scratch”不是复古式造轮子&#xff0c;而是剥离所有封装…

作者头像 李华
网站建设 2026/9/30 8:23:00

拆解《中华民谣》经典句:如何用古典意象写好流行歌词

“朝花夕拾杯中酒&#xff0c;寂寞的人在风雨后”——这句话我第一次听是在90年代&#xff0c;家里电视上放着点歌台&#xff0c;谢东在那首歌里一开口就是这句。后来很多年里&#xff0c;我偶尔会在不同的场合再听到它&#xff0c;超市、出租车广播、长辈的手机铃声&#xff0…

作者头像 李华