公司二-面试题

公司二

第一:你们项目中的Redis主要用在什么地方?

  1. 存储Token
  2. 空域转换需要在未来的某个时间点自动执行,如果使用数据库轮询,每分钟扫描一次全表,数据量大了效率很低。我的方案是用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 的区别

  1. resultType 用于简单结果映射,要求列名和属性名一致,MyBatis 自动完成映射;
  2. resultMap 用于复杂结果映射,可以手动指定列与属性的对应关系,还能处理一对一、一对多等关联关系。
  3. 实际开发中,简单查询用 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 来优化。

相关推荐
AI备案指南-满满1 小时前
算法备案审核不通过,一般是什么原因?
java·人工智能·算法·大模型备案·算法备案·大模型登记
秋天的怀念_David2 小时前
算法一千零一夜 · 第二十三夜 电力民生写成的曲线
java·程序人生·算法
吴名氏.2 小时前
Java转AI Agent教程(第五周)Prompt工程
java·人工智能·prompt·ai agent
8Qi82 小时前
DDD架构演进
java·分布式·mvc·ddd
paopaokaka_luck2 小时前
灾害应急物资管理系统(AI应急助手、Echarts可视化数据看板、捐赠入库联动、 物资全流程管理、库存变动追溯、灾情资讯与互动反馈、物资发放公示)
java·前端·javascript·spring boot·mysql·tomcat·echarts
SL_staff2 小时前
HR系统对接钉钉考勤总失败?根源在于缺乏业务语义级逻辑中枢
java·开源·全栈
程序边界2 小时前
一台数据集成软件的拆解笔记(下):一致性、容错、跑批与迁移,KFS 真正难的地方在这
java·前端·笔记
云和数据.ChenGuang3 小时前
Python 闭包详解:从原理到实战
linux·开发语言·人工智能·python·kubernetes
揣一尾游鱼yu3 小时前
内部类和Object笔记
java·开发语言·object·内部类