day1 IO流
day1
计划
- 今天学完IO
笔记
1.File
- File的3个构造方法
- 表示什么:路径,可以表示文件或文件夹
- 常用方法
- 判断,获取
- 创建,删除
- 遍历
- 不能读写数据,读写需要用到IO流
2.IO流
1.字节流
- 计算机数据的最小单位是字节
- 字节流可以操作任何文件
- 字节流读取文本文件,需要读取为
byte[],指定编码方式转为字符串,否则中文字符会出现乱码。 - 字节流没有缓冲区,可以用byte数组来做缓冲区,提升读取的效率
2.字符流
- 字符集有ASCII,GBK,Unicode等
- ASCII英文占用1字节
- GBK兼容ASCII,英文1字节,中文2字节。英文字节首位为0,中文高位字节首位为1,由此区分中英文
- Unicode编码方式多样
- UTF-8,1-4字节表示字符,英文1,中文3
- UTF-16,2或4字节表示字符
- UTF-32,4字节表示字符
- 字符串转为
bite[]可以指定编码方式,bite[]构建字符串也可以指定编码。
- 字符流底层通过解码器按指定字符集将字节转换为字符。以 UTF-8 为例,解码器会识别字节头,决定本次读取 1~4 个字节拼成一个字符。
- 字符流可以在定义时指定编码格式,未指定就会用默认编码。
- 字符流的输入,输出流都有缓冲区
- 输入流,会先填满缓冲区,优先读取缓冲区,读完缓冲区后才会再取数据填满缓冲区。即使文件清空,也会先读完缓冲区的数据。
- 输出流,会先存到缓冲区,如果不调用
flush()或close(),数据会一直卡在内存中,直到缓冲区满了才会写入文件。
day11 部门管理实战
day11
计划
- 前后端分离开发规范与REST风格
- 工程搭建与基础代码结构
- 部门管理CRUD功能实现
- 参数接收方式详解
- MyBatis结果映射与驼峰命名
- Nginx部署与前后端联调
- Logback日志技术
笔记
1.前后端分离开发
前后端分离
将工程拆分为前端工程和后端工程,各自独立开发、独立部署。前端通过异步请求获取数据,后端根据接口文档提供数据。
开发流程
- 需求分析 → 理解产品原型和需求文档
- 接口定义 → 查阅接口文档,明确地址、参数、响应
- 前后端并行开发 → 各自按接口文档实现
- 接口测试 → 使用Apifox/Postman等工具测试后端接口
- 前后端联调 → 前端请求后端接口,验证整体功能
2.RESTful风格
传统URL vs RESTful
| 操作 | 传统URL | RESTful URL |
|---|---|---|
| 查询 | /user/getById?id=1 GET |
/users/1 GET |
| 新增 | /user/saveUser POST |
/users POST |
| 修改 | /user/updateUser POST |
/users PUT |
| 删除 | /user/deleteUser?id=1 GET |
/users/1 DELETE |
RESTful核心规则
- URL定位资源:使用名词复数形式(如
/depts、/emps) - HTTP动词描述操作:GET查询、POST新增、PUT修改、DELETE删除
- 一句话总结:通过URL定位资源,通过HTTP请求方式描述操作
3.工程搭建
依赖引入
1 | <dependencies> |
application.yml配置
1 | spring: |
包结构
1 | com.zhang |
4.统一响应结果Result
1 | @Data |
5.部门管理CRUD
5.1 查询部门列表
Controller:
1 | @GetMapping("/depts") |
**Service:**调用Mapper查询所有部门。
Mapper:
1 | @Select("select id, name, create_time, update_time from dept") |
5.2 删除部门(根据ID)
请求方式: DELETE
请求路径: /depts?id=1
1 | @DeleteMapping("/depts") |
Mapper:
1 | @Delete("delete from dept where id=#{id}") |
5.3 新增部门
请求方式: POST
请求路径: /depts
请求参数: JSON格式 {"name":"研发部"}
1 | @PostMapping("/depts") |
**Service:**补全createTime和updateTime
1 | public void insert(Dept dept) { |
Mapper:
1 | @Insert("insert into dept(name,create_time,update_time) values(#{name},#{createTime},#{updateTime})") |
5.4 根据ID查询部门
请求方式: GET
请求路径: /depts/1(路径参数)
1 | @GetMapping("/depts/{id}") |
5.5 修改部门
请求方式: PUT
请求路径: /depts
请求参数: JSON格式 {"id":1,"name":"研发部"}
1 | @PutMapping("/depts") |
**Service:**更新updateTime
1 | public void update(Dept dept) { |
6.参数接收方式
6.1 简单参数接收
| 方式 | 代码示例 | 说明 |
|---|---|---|
| HttpServletRequest | request.getParameter("id") |
繁琐,需手动转换,不推荐 |
| @RequestParam | @RequestParam("id") Integer id |
指定参数名,推荐 |
| 形参同名 | Integer id(参数名与形参名一致时) |
最简洁,推荐 |
1 | // 方式一:@RequestParam |
6.2 JSON参数接收
使用@RequestBody注解,Spring自动将JSON数据反序列化为Java对象。
1 | public Result save(@RequestBody Dept dept) |
注意:JSON的key必须与实体类的属性名一致。
6.3 路径参数接收
使用@PathVariable注解,从URL路径中获取参数。
1 | @GetMapping("/depts/{id}") |
7.请求方式映射注解
| 注解 | 对应请求方式 | 用途 |
|---|---|---|
| @GetMapping | GET | 查询数据 |
| @PostMapping | POST | 新增数据 |
| @PutMapping | PUT | 修改数据 |
| @DeleteMapping | DELETE | 删除数据 |
| @RequestMapping | 所有方式 | 可通过method属性限定 |
推荐:使用@GetMapping等衍生注解,比@RequestMapping更简洁、语义更清晰。
类级别@RequestMapping
将公共路径抽取到类上,方法上的路径与类上的路径拼接形成完整请求路径。
1 | @RequestMapping("/depts") |
8.MyBatis结果映射
问题:属性名与字段名不一致
实体类属性(createTime)与数据库字段(create_time)命名风格不同,导致MyBatis无法自动封装。
三种解决方案
| 方案 | 代码示例 | 说明 |
|---|---|---|
| 手动映射 | @Results({@Result(column="create_time",property="createTime")}) |
灵活但繁琐 |
| SQL起别名 | select create_time createTime from dept |
简单直接 |
| 驼峰映射 | 配置map-underscore-to-camel-case: true |
推荐 |
驼峰命名配置
在application.yml中开启自动驼峰命名映射:
1 | mybatis: |
映射规则:create_time → createTime,update_time → updateTime
9.Nginx与前后端联调
Nginx核心功能
| 功能 | 说明 |
|---|---|
| Web服务器 | 托管静态资源(HTML、CSS、JS、图片) |
| 反向代理 | 转发客户端请求到后端服务器 |
| 负载均衡 | 将请求分发到多台后端服务器 |
请求流程
1 | 浏览器 → Nginx(:90) → 后端Tomcat(:8080) → Nginx → 浏览器 |
- 浏览器访问
http://localhost:90/api/depts - Nginx接收到请求,根据
/api/前缀匹配规则 - Nginx重写路径:
/api/depts→/depts - Nginx将请求转发到
http://localhost:8080/depts - 后端处理完返回数据,Nginx将结果返回给浏览器
Nginx关键配置
1 | server { |
配置说明:
| 配置项 | 作用 |
|---|---|
location / |
匹配所有路径,返回前端页面 |
try_files |
支持前端路由刷新不404 |
location ^~ /api/ |
精确匹配API前缀,不再检查正则 |
rewrite |
去掉/api/前缀,重写路径 |
proxy_pass |
转发请求到后端服务器 |
为什么使用Nginx
- 安全:后端Tomcat集群不直接暴露给前端
- 灵活:后端增减服务器对前端无感知
- 负载均衡:便于实现多服务器负载均衡
10.Apifox接口测试
Apifox是集成了API文档、调试、Mock、测试的协作平台。
使用步骤
- 创建项目,导入或编写接口文档
- 在”接口管理”中定义接口(路径、方法、参数、响应)
- 在”接口调试”中发送请求测试
- 在”Mock服务”中生成模拟数据供前端开发
- 支持GET/POST/PUT/DELETE等所有请求方式
11.Logback日志
为什么使用日志框架
| System.out.println | Logback |
|---|---|
| 硬编码,不灵活 | 可通过配置文件控制 |
| 只能输出到控制台 | 支持输出到文件 |
| 无日志级别 | 支持debug/info/warn/error级别 |
| 不便于维护 | 灵活配置格式和输出位置 |
Logback入门
1. 引入依赖(SpringBoot已内置)
2. 配置文件logback.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
3. 获取Logger对象
1 | // 方式一:声明Logger对象 |
4. 记录日志
1 | log.debug("调试信息"); |
日志级别
| 级别 | 说明 | 优先级 |
|---|---|---|
| trace | 追踪(很少使用) | 低 |
| debug | 调试 | ↓ |
| info | 运行信息 | ↓ |
| warn | 警告 | ↓ |
| error | 错误 | 高 |
规则:设置的级别越高,输出的日志越少。如设置info级别,则debug和trace级别不会输出。
12.核心知识点总结
| 知识点 | 说明 |
|---|---|
| 前后端分离 | 前端独立工程、后端独立工程,通过接口文档协作 |
| RESTful风格 | URL定位资源(复数名词),HTTP动词描述操作 |
| @GetMapping | GET请求查询数据 |
| @PostMapping | POST请求新增数据 |
| @PutMapping | PUT请求修改数据 |
| @DeleteMapping | DELETE请求删除数据 |
| @RequestBody | 接收JSON格式参数 |
| @RequestParam | 接收URL查询参数 |
| @PathVariable | 接收URL路径参数 |
| @RequestMapping | 类级别公共路径抽取 |
| Result | 统一响应封装类 |
| map-underscore-to-camel-case | 下划线自动转驼峰映射 |
| Nginx | 静态服务器+反向代理+负载均衡 |
| Apifox | 接口文档编写与测试工具 |
| Logback | 日志框架,支持控制台和文件输出 |
| 日志级别 | debug < info < warn < error |
day10 Java操作数据库
计划
- JDBC基础入门
- JDBC查询与增删改
- SQL注入与预编译SQL
- MyBatis快速入门
- MyBatis注解写法与XML映射
- SpringBoot配置文件与数据库连接池
笔记
1.JDBC概述
JDBC是什么
- JDBC:Java DataBase Connectivity,使用Java语言操作关系型数据库的一套API
- 本质:Sun公司定义的一套接口规范,数据库厂商提供驱动jar包实现接口
- 执行原理:Java程序调用JDBC接口,真正执行的是数据库驱动中的实现类
Java操作数据库技术
| 技术 | 说明 |
|---|---|
| JDBC | 最底层、最基础的数据库操作技术 |
| MyBatis | 基于JDBC封装的持久层框架,简化数据库开发 |
| MyBatisPlus | 在MyBatis基础上进一步增强 |
| Hibernate / SpringDataJPA | ORM思想更强的持久层框架 |
2.JDBC查询数据
查询需求
基于JDBC实现用户登录,本质是执行一条带用户名和密码条件的查询语句:
1 | select * from user where username = 'daqiao' and password = '123456'; |
准备数据表
1 | create table user( |
JDBC查询步骤
1 | Connection conn = DriverManager.getConnection( |
ResultSet结果集
- ResultSet:封装DQL查询语句返回的结果
- next():移动光标到下一行,有数据返回true,没有数据返回false
- getXxx(…):获取当前行指定列的数据,推荐使用列名获取
3.预编译SQL与SQL注入
静态SQL与预编译SQL
| 写法 | 示例 | 问题 |
|---|---|---|
| 静态SQL | username = 'daqiao' |
参数写死,灵活性差 |
| 字符串拼接SQL | 拼接用户输入 | 容易产生SQL注入 |
| 预编译SQL | username = ? |
安全、性能更高,推荐使用 |
PreparedStatement
1 | PreparedStatement pstmt = conn.prepareStatement( |
SQL注入
- SQL注入:通过控制输入内容,修改原本SQL语句含义,达到绕过校验或攻击服务器的目的
- 典型场景:登录功能中,如果使用字符串拼接SQL,输入特殊内容可能使条件恒成立
- 解决方式:使用预编译SQL,用户输入会被当成普通参数值处理,不再改变SQL结构
4.JDBC增删改数据
执行DML语句
1 | Connection conn = DriverManager.getConnection( |
executeQuery与executeUpdate
| 方法 | 执行语句 | 返回值 |
|---|---|---|
| executeQuery() | DQL查询语句 | ResultSet结果集 |
| executeUpdate() | DML增删改语句 | 影响的记录数 |
5.MyBatis概述
MyBatis是什么
- MyBatis:一款优秀的持久层框架,用于简化JDBC开发
- 持久层:数据访问层,负责操作数据库
- 框架:半成品软件,提供通用基础代码,提高开发效率
JDBC的不足
- 数据库连接信息硬编码在Java代码中
- 查询结果解析和对象封装繁琐
- 每次操作都要创建和关闭连接,资源浪费明显
MyBatis的改进
- 数据库连接信息放到SpringBoot配置文件中
- 查询结果由MyBatis自动映射为Java对象
- 底层使用数据库连接池复用连接
6.MyBatis快速入门
使用步骤
- 创建SpringBoot工程,引入
mybatis-spring-boot-starter和mysql-connector-j - 创建数据库表,并创建与表字段对应的实体类
- 在配置文件中配置数据库连接信息
- 编写Mapper接口,并在接口上添加
@Mapper - 选择注解方式或XML方式编写SQL
- 在业务代码或测试代码中注入Mapper接口,调用方法操作数据库
核心关系
| 组成 | 作用 | 复习重点 |
|---|---|---|
| 实体类 | 封装表中一行数据 | 属性名尽量与字段名一致 |
| Mapper接口 | 定义数据库操作方法 | 方法名表达清楚要执行的操作 |
| SQL语句 | 真正操作数据库 | 可写在注解中,也可写在XML中 |
| 配置文件 | 配置数据源和MyBatis行为 | 数据库连接四要素、日志输出 |
Mapper接口基本模板
1 | @Mapper |
细节:
- Mapper接口不需要自己写实现类,MyBatis运行时会生成代理对象
@Mapper的作用是让MyBatis识别该接口,并把代理对象交给Spring IOC容器管理- DML语句可以用
int作为返回值,表示影响的记录数 - 查询多条数据用
List<实体类>,查询单条数据用实体类
7.MyBatis注解写法
适用场景
注解写法适合简单SQL,例如单表的基础增删改查。优点是直观、文件少;缺点是复杂SQL写在Java注解中可读性差。
查询
1 | @Select("select * from user") |
细节:
@Select用于写查询语句- 多条件查询时,建议使用
@Param给参数命名,SQL中通过#{参数名}取值 - 查询结果字段名与实体类属性名一致时,MyBatis可以自动封装
删除
1 | @Delete("delete from user where id = #{id}") |
细节:
#{id}不是字符串拼接,而是预编译占位符- 返回值
int表示删除了几条记录
新增
1 | @Insert("insert into user(username, password, name, age) values(#{username}, #{password}, #{name}, #{age})") |
细节:
- 参数是对象时,
#{username}表示调用对象的getUsername()取值 - 自增主键字段通常不需要手动插入
- 字段顺序要与values中的参数顺序保持一致
修改
1 | @Update("update user set username = #{username}, password = #{password}, name = #{name}, age = #{age} where id = #{id}") |
细节:
- 修改语句必须注意
where条件,缺少条件会更新整张表 - 修改对象必须包含用于定位数据的主键值
8.MyBatis XML映射写法
适用场景
XML写法适合复杂SQL,例如多条件动态查询、多表查询、复杂字段映射。优点是SQL集中、可读性更好;缺点是接口和XML之间要严格对应。
XML开发规范
| 规范 | 要求 |
|---|---|
| 文件位置 | XML映射文件与Mapper接口包路径保持一致 |
| 文件名称 | XML文件名与Mapper接口名一致,如UserMapper.xml |
| namespace | 必须写Mapper接口的全限定名 |
| SQL的id | 必须与Mapper接口方法名一致 |
| resultType | 查询返回单条记录要封装成的实体类类型 |
XML基本模板
1 | <?xml version="1.0" encoding="UTF-8" ?> |
查询写法
1 | <select id="findAll" resultType="com.zhang.pojo.User"> |
细节:
resultType写的是单条记录封装成什么类型,不是List<User>- 如果Mapper方法返回
List<User>,XML中仍然写resultType="com.zhang.pojo.User" id必须和接口方法名一致,否则MyBatis找不到对应SQL
增删改写法
1 | <insert id="insert"> |
细节:
<insert>、<update>、<delete>标签一般不需要写resultType- DML执行后返回影响行数,Mapper接口方法可以写
int - XML和注解不要同时给同一个方法配置SQL,否则会冲突或造成理解混乱
9.注解写法与XML写法对比
| 对比点 | 注解写法 | XML写法 |
|---|---|---|
| SQL位置 | Mapper接口方法上 | resources下的Mapper.xml中 |
| 适合场景 | 简单增删改查 | 复杂SQL、动态SQL、多表查询 |
| 优点 | 写法直接,文件少 | SQL更清晰,便于维护复杂语句 |
| 缺点 | SQL复杂时可读性差 | 配置规则多,需要接口和XML对应 |
| 选择建议 | 入门和简单SQL优先使用 | 复杂业务SQL优先使用 |
10.MyBatis参数传递细节
#{...}与${...}
| 符号 | 本质 | 结果 | 使用建议 |
|---|---|---|---|
#{...} |
参数占位符 | 会替换为?,走预编译SQL |
传递普通参数时强烈推荐 |
${...} |
字符串拼接 | 直接拼接到SQL语句中 | 只有动态表名、字段名等特殊场景才考虑 |
为什么推荐#{...}
- 可以防止SQL注入
- 可以使用预编译SQL,提高性能
- 参数值会被当成普通数据处理,不会改变SQL结构
参数来源
| Mapper方法参数 | SQL中取值方式 |
|---|---|
| 单个简单参数 | #{参数名}或#{任意名}通常都能取到 |
| 多个简单参数 | 推荐使用@Param("名称")后通过#{名称}取值 |
| 对象参数 | 通过#{属性名}取对象属性值 |
11.数据库连接池
连接池作用
- 数据库连接池是一个容器,负责分配和管理数据库连接
- 程序启动时,连接池中会提前创建一定数量的Connection对象
- 执行SQL时从连接池获取连接,执行完毕后归还连接池
- 空闲过久的连接可以被自动释放,减少连接泄露风险
连接池优点
- 资源重用
- 提升系统响应速度
- 避免频繁创建和销毁连接
- 降低数据库连接遗漏风险
常见连接池
| 连接池 | 说明 |
|---|---|
| Hikari | SpringBoot默认连接池,性能好 |
| Druid | 阿里巴巴开源连接池,功能强大 |
| C3P0 | 早期常见连接池 |
| DBCP | Apache提供的连接池 |
12.SpringBoot配置文件
properties与yml
| 配置文件 | 格式 | 特点 |
|---|---|---|
| application.properties | key=value |
简单直接,配置多时层级不够清晰 |
| application.yml / application.yaml | 缩进表示层级 | 简洁清晰,以数据为中心,推荐使用 |
yml语法规则
- 大小写敏感
- 数值前必须有空格作为分隔符
- 使用缩进表示层级关系
- 缩进不能使用Tab,只能使用空格
- 相同层级左侧对齐
#表示注释- 以0开头的值建议使用引号包裹,避免被识别为八进制
数据源配置模板
1 | spring: |
MyBatis日志配置
1 | mybatis: |
开启后,控制台可以看到MyBatis执行的SQL语句、参数和结果,方便排查数据库操作问题。
13.复习时容易忽略的细节
- Mapper接口只定义方法,不写实现类,实现类由MyBatis动态代理生成
@Mapper不能忘,否则Mapper接口不会被MyBatis识别并交给Spring管理- 注解和XML是两种SQL配置方式,同一个方法不要两边都写SQL
- XML中的
namespace必须等于Mapper接口全限定名 - XML中SQL标签的
id必须等于Mapper接口的方法名 - 查询集合时,XML的
resultType仍写集合中单个元素的类型 - DML语句返回
int可以知道影响了几条记录 - 修改和删除一定要注意
where条件,避免影响整张表 - 普通参数传递优先使用
#{...},不要为了省事使用${...} - 开启MyBatis日志后,可以在控制台观察最终执行的SQL和参数
14.核心知识点总结
| 知识点 | 说明 |
|---|---|
| JDBC | Java操作关系型数据库的底层API |
| DriverManager | 获取数据库连接 |
| Connection | 数据库连接对象 |
| PreparedStatement | 预编译SQL执行对象 |
| ResultSet | 查询结果集对象 |
| executeQuery() | 执行DQL查询语句 |
| executeUpdate() | 执行DML增删改语句 |
| SQL注入 | 通过输入改变SQL语义的攻击方式 |
| MyBatis | 简化JDBC开发的持久层框架 |
| @Mapper | 声明Mapper接口,交给MyBatis和Spring管理 |
| 注解写法 | 通过@Select、@Insert、@Update、@Delete在接口方法上写SQL |
| #{…} | 参数占位符,生成预编译SQL |
| ${…} | 字符串拼接符,存在注入风险 |
| XML映射 | 将SQL写在XML中,适合复杂SQL |
| Hikari | SpringBoot默认数据库连接池 |
| application.yml | SpringBoot推荐配置文件格式 |
day2 IO流
day2
计划
- 学完IO流
笔记
1.缓冲流
字节缓冲流
- 缓冲流默认缓冲区大小为8192字节(8KB)
- 使用方式:在基本流外包装一层缓冲流
- 关闭缓冲流时,底层基本流也会自动关闭
- 性能对比
- 缓冲流逐字节读取复制:35ms(内部缓冲区已优化磁盘I/O)
- 缓冲流配合 byte[] 数组读取复制:4ms
- 缓冲流与缓冲数组同时使用是不必要的
- 缓冲流内部已有 8KB 缓冲区,逐字节读取时已通过内部缓冲区大幅减少磁盘 I/O,这是性能提升的核心
- 在缓冲流之上再使用 byte[] 数组,相当于多了一层冗余的缓冲层级,增加了内存占用和代码复杂度
- 实际开发中,单独使用缓冲流即可,或直接使用基本流配合自己的缓冲区,没必要两层都用
字符缓冲流
- BufferedReader 特有方法:
readLine(),读取一行文本,遇到文件末尾返回 null - BufferedWriter 特有方法:
newLine(),用于多平台换行(Windows: \r\n, Linux: \n) - 字符缓冲流也可以指定编码格式
2.转换流
- InputStreamReader:将字节输入流转换为字符输入流
- OutputStreamWriter:将字节输出流转换为字符输出流
- 可以指定编码格式,解决乱码问题
- 常与缓冲流配合使用,形成:
BufferedReader -> InputStreamReader -> FileInputStream - Java 11+ FileReader/FileWriter 已支持指定编码,但转换流仍有用武之地
- 处理非文件来源的字节流(如网络流、内存流)
- 需要精确控制编码转换场景
3.序列化流与反序列化流
- 对象序列化:将对象写入文件,需要实现
Serializable接口 - 对象反序列化:从文件读取对象
serialVersionUID:序列化版本号,确保反序列化时类版本一致- 如果类结构发生变化(如新增字段),版本号不匹配会抛出
InvalidClassException - 建议显式声明版本号,避免编译器自动生成导致的兼容性问题
- 如果类结构发生变化(如新增字段),版本号不匹配会抛出
transient关键字:修饰的成员变量不参与序列化,反序列化时为默认值- 序列化集合对象:将整个集合写入文件,便于批量操作
4.压缩流与解压缩流
ZipEntry
- 表示压缩包里的每个文件或目录
- 压缩流与解压流中有一个当前条目的概念,就是你最近获取到的或创建的ZipEntry对象
- 所以必须要及时关闭当前条目
解压缩
- 使用
ZipInputStream读取压缩包 getNextEntry()获取下一个 ZipEntry- 判断是目录还是文件,分别处理
- 读取完成后调用
closeEntry()关闭当前条目
压缩
- 使用
ZipOutputStream写入压缩包 putNextEntry()创建新的 ZipEntry- 递归处理目录结构,保持原有的层级关系
- 遍历目录下所有文件和子目录
- 文件则直接写入压缩条目
- 目录则递归调用,传入更新后的路径名
- 每个文件写入完成后调用
closeEntry()
5.打印流
- PrintStream:字节打印流,
System.out就是 PrintStream 的实例 - PrintWriter:字符打印流
- 特点
- 可以直接打印各种类型的数据(int、double、String、对象等)
- 可以设置自动刷新(autoFlush),调用
println()时自动刷新缓冲区,换行。
6.URL网络读取
- 使用
URL和URLConnection建立网络连接 - 通过
getInputStream()获取输入流 - 使用
InputStreamReader按字符读取网页内容 - 可以配合第三方工具包(如 Hutool)简化文件写入操作
7.工具包
- commons-io
- hutool
- 两个都是第三方工具包,提供了很多包装好的工具,导入即可直接使用。
day20 Redis入门与店铺状态设置
前几天陆陆续续写了苍穹外卖的员工管理,菜品,分类,套餐管理等,未坚持每日学习,未写复盘
1. 今天做了什么
(day5的内容)
- Redis入门
- 安装
- Redis里的数据类型:字符串,哈希,集合,有序集合,列表
- Redis操作的命令
- java程序中操作Redis数据库:使用Spring Data Redis
- 配置
- 使用
- 店铺相关接口 , 使用Redis存储
- 设置店铺营业状态
- 获取店铺状态(用户端与管理端)
- 接口分组展示的配置
2. 核心知识点
Redis的基础使用
3. 深度思考
- 为什么使用Redis
- Redis将数据存在内存中,访问效率更高,可用于存方高频使用的数据
- 店铺状态只有一个值,使用Redis需要为它单独创建一个一行一列的表,而使用Redis这种键值对的方式就很方便
4. 踩过的坑
- 获取店铺状态有用户端与管理端,必须在使用@RestController(“userShopController”)注解是对其命名进行区分,否则会因为Bean对象名冲突报错
5. 遗留问题 / 想不通
- 通过@Bean注解在配置类中配置的Bean,使用范围是?
- 只有该配置类在springboot扫描范围内,才会加入IOC容器管理
- 只有被IOC容器管理的类,其非静态属性,才可以使用@Autowired注入Bean
redisTemplate.setKeySerializer(new StringRedisSerializer());的作用- 对key进行序列化
- java操作Redis时,使用java字符串作为key,所以为Key指定字符串序列化器,可以确保java字符串存进Redis还是该字符串
- 若不指定序列化器,使用默认序列化器,存进Redis的key与java字符串会不一致
- 也可以为Value设置序列化器
// 设置 Value 的序列化器(通常用 Jackson,存 JSON 格式,方便其他语言读取)
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
1 | @Bean |
day22 缓存
1. 今天做了什么
外卖day7的一部分
- 缓存菜品
- 缓存套餐
2. 核心知识点
- 两种实现缓存的方式
- 使用Redis自己实现缓存
- 根据分类id查询菜品,将分类id作为key,将查询到的集合作为value
- 查询时先查缓存是否存在,存在直接返回,不存在则查询后存入缓存返回
- 一旦管理端发生新增,修改,删除,起售停售,都要对缓存进行清理
- 使用Spring Cache实现缓存
- 完全使用注解实现,很方便
- 在启动类使用注解进行开启
- 在相应查询接口上添加注解,实现查询时先查缓存是否存在,存在直接返回,不存在则查询后存入缓存返回
- 在管理端新增,修改,删除操作添加清除缓存的注解
- Spring Cache底层可以用多种方式实现,这里使用Redis,添加了Redis的依赖,可以自动识别
- 使用Redis自己实现缓存
3. 深度思考
- 为什么要做缓存
- 在业务执行过程中,数据库查询操作从磁盘读取数据,比较耗时
- 用户使用量比较大时加载较慢,影响使用体验
- 在Redis中做缓存,从内存中读取速度很快
4. 踩过的坑
- redisTemplate.delete()不支持通配符,redisTemplate.keys()支持,先查询keys,再删除
1 | @ApiOperation("修改菜品") |
- Spring Cache的@CacheEvict注解,清除所有setmealcache的缓存:
1 | @CacheEvict(cacheNames = "setmealcache", allEntries = true) |
5. 遗留问题 / 想不通
day21 微信登录
1. 今天做了什么
(外卖day6的内容)
- HttpClient的使用
- 微信小程序开发入门
- 项目目录结构
- 获取头像与昵称
- 获取授权码
- 微信登录
- 流程:小程序获取授权码->携带授权码向服务端发起请求->服务端使用授权码向微信的接口发起请求,获取openid(微信用户唯一标识)->查询若为新用户则1创建->创建jwt令牌,与用户基本信息一起返回给小程序
- 设置拦截器,对除登录与获取营业状态的其他来自用户端的请求进行令牌校验
2. 核心知识点
- HttpClient的使用
- 登录流程
3. 深度思考
4. 踩过的坑
- 从json字符串中解析出信息:
1 | JSONObject jsonObject = JSON.parseObject(resultJson);//将json字符串转换为json对象 |
5. 遗留问题 / 想不通
day3 多线程
计划
多线程
总结
1.线程创建
3种方式
- 继承Thread
- 实现Runnable
- 实现Callable
特点:
3可以有返回值,1,2无返回值
1因为单继承,无法继承其他类
方法:
设置/获取线程名,获取当前线程名,出让,插入,睡眠等
2.同步安全问题
原因
- 大多操作系统对线程采用抢占式调度
- 线程之间会争夺CPU执行权
- 多个线程对同一资源进行操作的过程中,CUP执行权随时会被夺走,被其他线程占用,从而造成安全问题
解决方案
- 对操作共享资源的代码采用锁,只有线程拿到锁,才能进入,执行代码,操作相应资源。未拿到锁的线程即使拿到CPU执行权也无法进入。
- 同步代码块
- 对一段代码设置锁,锁对象可以自己指定
- 代码执行完锁会自动释放
- 同步方法
- 非静态方法以this作为锁对象
- 静态方法以类名.class做为锁对象
- Lock锁
- 需要手动设置锁与释放锁
- 为确保锁被释放,可将释放语句写进try的finally中
注意:
- CPU执行权的抢夺与锁的抢夺是独立的
- 线程只有拿到CPU执行权才可以去抢夺锁
- 线程即使抢到锁,也随时可能被夺走CPU执行权,但因为锁未释放,其他线程无法进入同步代码块,只有等下次锁被释放
- 避免锁的嵌套,防止出现死锁,即外层锁与内层锁被不同线程拿到,全部陷入阻塞
3.等待唤醒机制
流程
- 生产者产出资源,消费者消耗资源
- 生产者
- 有资源:等待
- 无资源
- 生产资源
- 唤醒正在等待的线程(消费者)
- 消费者
- 无资源:等待
- 有资源:
- 消耗资源
- 唤醒正在等待的进程(生产者)
细节
- 等待与唤醒都需要通过同个锁对象(任意类),唤醒的时候才能唤醒所有通过该锁对象进入等待的线程
- 进程进入等待时,释放锁,并交出CPU执行权。在被唤醒时,继续抢夺锁,拿到锁后继续执行后续语句。
实现
- 自己用同步代码块synchronized实现,对操作共享资源的代码设置锁
- 可以使用阻塞队列,阻塞队列内时线程安全的。内部也实现了等待与唤醒。没一个放入与取出操作时一定会成功执行的,无法放入进入等待,下次唤醒会继续放入。
4.线程池
意义
- 降低资源消耗:复用已创建的线程,避免频繁创建和销毁线程(
new Thread+start())带来的性能开销 - 提高响应速度:任务到达时,无需等待线程创建,可直接使用空闲线程立即执行
- 提高线程可管理性:线程是稀缺资源,统一分配、调优和监控,防止无限制创建导致系统崩溃(OOM)
核心参数(ThreadPoolExecutor 的7个参数)
- 核心线程数(corePoolSize):线程池中一直存活的线程数,即使空闲也不会被回收(除非设置了
allowCoreThreadTimeOut) - 最大线程数(maximumPoolSize):线程池中允许的最大线程数
- 存活时间(keepAliveTime):非核心线程空闲超过该时间会被回收
- 时间单位(unit):
keepAliveTime的时间单位(如TimeUnit.SECONDS) - 阻塞队列(workQueue):存放等待执行的任务(如
ArrayBlockingQueue、LinkedBlockingQueue) - 线程工厂(threadFactory):创建线程的工厂,可自定义线程名、优先级等(默认
Executors.defaultThreadFactory()) - 拒绝策略(handler):当线程池和队列都满时,对新提交的任务的处理方式
执行流程(任务提交后的流转)
- 核心线程:当前线程数 < 核心线程数 → 创建新线程执行任务
- 阻塞队列:核心线程已满 → 任务放入队列等待
- 最大线程:队列已满 → 创建非核心线程执行任务(直到达到最大线程数)
- 拒绝策略:最大线程也满了 → 执行拒绝策略
注意:流程是 核心 → 队列 → 最大,而非很多人误以为的核心 → 最大 → 队列
拒绝策略(4种内置实现)
AbortPolicy(默认):直接抛出RejectedExecutionException,阻止系统正常运行CallerRunsPolicy:由调用者线程(提交任务的线程)自己执行该任务,提供反馈机制,减缓任务提交速度DiscardPolicy:直接丢弃任务,不抛出异常(丢失任务)DiscardOldestPolicy:丢弃队列中最旧的任务,然后重试提交当前任务
提交任务的两种方式
execute(Runnable command)- 提交无返回值的任务
- 异常直接抛出(控制台打印),调用者无法捕获
submit(Callable/Runnable task)- 提交有返回值(
Callable)或无返回值(Runnable)的任务 - 返回
Future对象,可通过future.get()获取结果或捕获异常 Runnable提交时,Future.get()返回nullRunnable+ 指定结果:submit(Runnable task, T result),Future.get()返回指定的result
- 提交有返回值(
注意
- 线程池中的线程是复用的:底层
Worker通过while循环不断从队列中取任务执行,执行完一个任务后不会销毁,而是继续取下一个任务 Thread对象与内核线程:Thread对象只是堆内存中的普通对象,真正执行能力来自操作系统内核线程(LWP)。线程池复用的是内核线程,而不是Thread对象本身- 不能直接操作池中某个线程:线程池封装了线程的个体操作,应通过
Future控制任务(如future.cancel(true)中断任务),而非直接操作线程 execute与submit底层关系:submit底层也是调用execute,但会将Runnable/Callable包装成RunnableFuture(即FutureTask),从而支持返回值和异常捕获- 线程池关闭:使用
shutdown()优雅关闭(不再接收新任务,等待已提交任务执行完毕),或shutdownNow()立即停止(尝试中断正在执行的任务,返回未执行的任务列表)