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