公司二
第一:你们项目中的Redis主要用在什么地方?
- 存储Token
- 空域转换需要在未来的某个时间点自动执行,如果使用数据库轮询,每分钟扫描一次全表,数据量大了效率很低。我的方案是用Redis ZSet作为延时队列:提交转换任务时,以生效时间戳作为score写入ZSet,同时把任务详情存入String缓存。AirspaceConvertScheduler定时任务每分钟扫描ZSet中score小于等于当前时间的记录,取出后执行空域类型更新、生成航空情报、发送席位消息。如果生效时间是立即,则不走ZSet直接执行。执行后通过Redis key做幂等保护,防止重复执行。
第二:Redis有哪些基本的数据类型?
第三:Redis的ZSet排序主要用什么数据结构?
跳表
第四:AOP的作用是什么?
AOP一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为"切面"(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
第五:我们用到Mybatis,新增完一条数据之后,我们要获取到新增数据的id,用Mybatis要怎么操作?
在 <insert> 标签上配 useGeneratedKeys="true" 和 keyProperty="id",它底层通过 JDBC 的 Statement.getGeneratedKeys() 在 INSERT 后获取自增主键,再通过反射回填到实体类的 id 属性上,执行完 insert 直接从原对象调 getId() 即可。
第六:MyBatis 中 resultMap 与 resultType 的区别
- resultType 用于简单结果映射,要求列名和属性名一致,MyBatis 自动完成映射;
- resultMap 用于复杂结果映射,可以手动指定列与属性的对应关系,还能处理一对一、一对多等关联关系。
- 实际开发中,简单查询用 resultType 更简洁,涉及多表关联或字段名不匹配时用 resultMap 更灵活。另外 resultMap 支持继承复用,配合延迟加载能优化关联查询性能。
第七:做过sql优化吗?
我负责低空空域飞行服务平台,数据库用达梦 DM8。有个审批列表查询,关联 biz_task_approval_record 审批记录表,数据量 300 万条,接口 RT 从 200ms 飙到 4-6 秒。
我 EXPLAIN 看执行计划,发现走的是 CSCN 全表扫描**(聚集索引全扫描)**,原因是查询按 user_id、biz_type 过滤、再按 create_time 排序取最新记录,但表上只有主键索引。
我建了 (user_id, biz_type, create_time) 复合索引,等值字段在前、排序字段在后,利用 B+ 树最左前缀和索引有序性,CSCN 变成 SSEK**(二级索引定位查找)**
优化后 RT 从 4-6 秒降到 100ms 以内,CPU 从 80% 降到 30% 以下。核心体会是:调优关键是理解达梦二级索引存 ROWID、需要 BLKUP 回表,所以索引字段顺序和覆盖范围直接决定性能。
第八:常见的索引失效场景有哪些?
第九:最左前缀法则是什么?如果是abc,ac索引也没有生效吗?cba有没有生效?
ac 部分生效(只有 a 走索引定位),cba 完全生效(优化器重排)。
第十:数据库里面有个类似if else的语法是什么?
CASE WHEN 是 SQL 中的条件判断表达式 ,作用类似编程语言中的 if-else 或 switch-case,用于在查询时根据条件返回不同的值。
核心含义
- CASE:开始一个条件判断
- WHEN:定义一个条件
- THEN:条件成立时返回的结果
- ELSE:所有条件都不满足时返回的默认值
- END:结束条件判断
1. 简单 CASE(等值匹配)
类似 switch-case,判断字段等于某个值:
SQL
sql
CASE 字段
WHEN 值1 THEN 结果1
WHEN 值2 THEN 结果2
ELSE 默认结果
END
示例:
SQL
sql
SELECT
name,
CASE status
WHEN 1 THEN '启用'
WHEN 0 THEN '禁用'
ELSE '未知'
END AS status_name
FROM sys_user;
第十一:如果我要查询一个日期,要把它格式化,要怎么操作?就比如说我数据库里是日期加时分秒,我只要他的日期,不要时分秒。
1. DATE() 函数(最常用)
直接截取日期部分,返回 DATE 类型:
SQL
sql
SELECT DATE(create_time) FROM orders;
-- 结果:2026-08-06
2. DATE_FORMAT() 函数(灵活定制格式)
SQL
sql
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') FROM orders;
-- 结果:2026-08-06
SELECT DATE_FORMAT(create_time, '%Y年%m月%d日') FROM orders;
-- 结果:2026年08月06日
第十二:SpringMVC里面,比如说我的请求路径里面是这样:getbyId/42342343242,我要怎么获取到这个Id?
@PathVariable注解
第十三:你用过那个Jackson的框架吗?
Jackson 是 Java 生态中最流行的高性能 JSON 处理库之一。它主要用于在 Java 对象和 JSON(或 XML)数据之间进行高效的序列化与反序列化,并且是 Spring Boot 框架默认内置的 JSON 解析工具。
第十四:部署了集群可以用synchronized锁吗?和分布式锁有什么区别?
单实例用 synchronized 够了,集群环境必须用分布式锁。不是 synchronized 不能用,而是它的作用域到不了别的 JVM。
第十五:stream流用的多吗?
Java Stream 是声明式数据处理管道,分三段:数据源 → 中间操作(惰性,不执行) → 终端操作(触发执行)。惰性求值是核心 :中间操作只构建链不处理数据,终端操作调用时才一次性启动,且元素垂直流动逐个穿过整条链。中间操作分无状态(filter/map)和有状态(sorted/distinct),短路操作(findFirst/anyMatch)可提前终止。底层用内部迭代,支持 parallelStream 并行。注意:只能消费一次、不能修改数据源、避免副作用。
第十六:你要把一个List转成map,并且你要保持顺序,你要怎么转?
用LinkedHashMap
java
Map<Long, User> map = new LinkedHashMap<>();
for (User u : list) {
map.putIfAbsent(u.getId(), u); // 重复 key 保留第一个
}
第十七:@NotNull和@NotBlack注解有什么区别?
@NotNull 只校验不为 null,空字符串和纯空格都能通过;@NotBlank 更严格,不仅不能为 null,还会去掉首尾空格后判断长度必须大于零,能拦截纯空格输入。所以严格程度是 @NotBlank > @NotNull,实际开发中用户名、密码这类必填文本字段应优先用 @NotBlank。
第十八:你了解过哪些设计模式?
Redisson 客户端和自定义线程池用 Spring 单例管理,避免重复创建;统一响应体用工厂方法封装。
第二十:事务,就比如说我们加了@Tranction注解没有效果,是什么情况?
一句话总结
6 大失效场景:非 public、同类调用、异常被吞、非 RuntimeException、非 Spring 管理、数据源不支持。
6 种失效场景详解
场景 1:方法不是 public
Java
java
// ❌ 失效:@Transactional 只能用在 public 方法上
@Transactional
private void updateData() { ... }
// ✅ 有效:public 方法
@Transactional
public void updateData() { ... }
原因:Spring AOP 默认使用 JDK 动态代理或 CGLIB 代理,非 public 方法不会被代理拦截。
场景 2:同类方法内部调用
Java
java
@Service
public class MyService {
// ✅ 这个有事务
@Transactional
public void methodA() {
methodB(); // ❌ methodB 的事务失效!
}
@Transactional
public void methodB() { ... }
}
原因:内部调用不走代理,直接调用原方法,事务注解没生效。
解决:
Java
java
// 方法1:注入自己(用代理对象调用)
@Autowired
private MyService self;
public void methodA() {
self.methodB(); // ✅ 通过代理对象调用
}
// 方法2:把 methodB 放到另一个 Service 中
场景 3:异常被 catch 住了,没有抛出
Java
java
@Transactional
public void updateData() {
try {
// 数据库操作...
} catch (Exception e) {
log.error("出错了", e);
// ❌ 异常被吞了,事务不会回滚
}
}
原因:Spring 要捕获到异常才会触发回滚,你 catch 住了就相当于"告诉 Spring 没有异常"。
解决:
Java
java
catch (Exception e) {
log.error("出错了", e);
throw e; // ✅ 重新抛出异常
}
场景 4:抛出的是 checked 异常(非 RuntimeException)
Java
java
@Transactional
public void updateData() throws IOException { // IOException 是 checked 异常
// 数据库操作...
throw new IOException("出错了"); // ❌ 默认不回滚
}
原因 :@Transactional 默认只回滚 RuntimeException 和 Error,不会回滚 checked 异常。
解决 :指定 rollbackFor 属性
Java
java
@Transactional(rollbackFor = Exception.class) // ✅ 所有异常都回滚
public void updateData() throws IOException { ... }
场景 5:类没有被 Spring 管理
Java
java
// ❌ 没有 @Service/@Component 等注解,不是 Spring Bean
public class MyService {
@Transactional
public void updateData() { ... }
}
// ✅ 必须是 Spring Bean
@Service
public class MyService {
@Transactional
public void updateData() { ... }
}
原因:只有 Spring 管理的 Bean 才会被 AOP 代理,事务才能生效。
场景 6:数据库引擎不支持事务
SQL
sql
-- ❌ MyISAM 引擎不支持事务
CREATE TABLE my_table (
id INT PRIMARY KEY
) ENGINE=MyISAM;
-- ✅ InnoDB 引擎支持事务(MySQL 5.5+ 默认)
CREATE TABLE my_table (
id INT PRIMARY KEY
) ENGINE=InnoDB;
原因:事务需要数据库引擎支持,MyISAM、MEMORY 等引擎不支持事务。
第一,方法不是 public ------ 注解只能用在 public 方法上; 第二,同类方法内部调用 ------ 不走代理,事务不生效; 第三,异常被 catch 住了 ------ Spring 捕获不到异常,不会回滚; 第四,抛出的是 checked 异常 ------ 默认只回滚 RuntimeException,需要指定 rollbackFor; 第五,类不是 Spring Bean ------ 没被管理就没有 AOP 代理; 第六,数据库引擎不支持事务 ------ 比如 MyISAM。
第二十一:你们项目里是怎么保证幂等性的?就比如说我前端提交一个新增表单可能会点很多下,这个幂等性是怎么保证的?
如上图,原理就这么简单:
- 第 1 次请求到达时,Token 还在 Redis 里 → 删掉 Token → 正常插入数据 → 成功
- 第 2、3 次请求到达时,Token 已经被第一次删掉了 → 查不到 → 直接拒绝
所以不管用户点多少次,只有第一次能真正走到业务逻辑,后面的全部被挡在 Token 校验这一关。
也就是说我们是通过Token存在Redis来解决的?
对,Token 存在 Redis 里是核心。整个流程就是围绕 Redis 里这个 Token 的"有"和"没有"来判断的:
- 打开页面时 → 往 Redis 里放一个 Token
- 第一次提交时 → 从 Redis 里删掉这个 Token,然后处理业务
- 后续重复提交时 → Redis 里已经找不到了 → 拒绝
第二十二:比如我批量新增10条数据,用的是Mybatisplus的IService.saveBatch(),这个API的底层实现是什么?
saveBatch 底层是 ExecutorType.BATCH + for 循环 addBatch + 分批 flush 的"伪批量",最终发的是同一条单行预编译 INSERT 重复 N 次,而非多值拼接;加 rewriteBatchedStatements=true 才会被驱动重写成真正的多值 INSERT 提速。
第二十三:你们是怎么做分页的,分页查询用的Mybatisplus的话,他的最终sql是什么?
MyBatis-Plus 的 selectPage 最终会向数据库发两条 SQL :一条 COUNT 查总数,一条 LIMIT 查数据。核心是 PaginationInnerInterceptor 拦截器,它拦截 boundSql 做改写------先把原 SQL 改成 SELECT COUNT(*) 并去掉 ORDER BY 拿到 total,total 为 0 就跳过数据查询直接返回空列表,否则在原 SQL 末尾按数据库方言拼接 LIMIT offset, size。要注意两点:一是 LIMIT 是方言相关的,拦截器靠 DbType 适配,MySQL 用 LIMIT、Oracle 用 ROWNUM,多数据库环境要指定清楚;二是 COUNT 优化有边界,简单查询能优化掉 LEFT JOIN,但遇到 GROUP BY 就没法改写会退化成子查询套 count,性能差。最后深分页 LIMIT 100万, 10 仍是瓶颈,我会用游标分页 WHERE id > lastId ORDER BY id LIMIT 10 来优化。
SQL 改成 SELECT COUNT(*) 并去掉 ORDER BY 拿到 total,total 为 0 就跳过数据查询直接返回空列表,否则在原 SQL 末尾按数据库方言拼接 LIMIT offset, size。要注意两点:一是 LIMIT 是方言相关的,拦截器靠 DbType 适配,MySQL 用 LIMIT、Oracle 用 ROWNUM,多数据库环境要指定清楚;二是 COUNT 优化有边界,简单查询能优化掉 LEFT JOIN,但遇到 GROUP BY 就没法改写会退化成子查询套 count,性能差。最后深分页 LIMIT 100万, 10 仍是瓶颈,我会用游标分页 WHERE id > lastId ORDER BY id LIMIT 10 来优化。