第一部分: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 | 不把数据转为字符串 |