面经Java
怎么用MyBatis在几s内内插入百万条数据?
使用MyBatis的ExecutorType.Batch执行器,手动控制事务与提交频率,利用批量SQL插入+合理分批(每次1000条防止内存溢出)。
代码大概就是在对象中注入一个SqlSessionFactory,然后在相应的方法中调用sqlSessionFactory.openSession(ExecutorType.BATCH, false)传入执行器,然后调用getMapper方法传入Mapper的class对象获取相应的Mapper,然后固定batchSize = 1000,for循环遍历要插入的数据,if判断每1000条提交一次(i % batchSize == n),最后执行到for循环外部处理一下最后一批的提交。
// 假设我们有一个简单的实体类 User(id, name, age)@Autowired
private SqlSessionFactory sqlSessionFactory;public void batchInsertUsers(List<User> userList) {// 1. 使用 BATCH ExecutorTypetry (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {UserMapper mapper = session.getMapper(UserMapper.class);int batchSize = 1000; // 每次提交 1000 条for (int i = 0; i < userList.size(); i++) {mapper.insertUser(userList.get(i));// 每 1000 条提交一次if (i > 0 && i % batchSize == 0) {session.flushStatements(); // 刷新批处理语句session.commit(); // 提交事务session.clearCache(); // 清理一级缓存,防止 OOM}}// 处理最后一批未提交的session.flushStatements();session.commit();session.clearCache();}
}
数据库层的优化方面:
innodb_buffer_pool_size设置的尽量大提高缓存能力,控制事务提交时日志刷盘的时机为innodb_flush_log_at_trx_commit = 2,每次提交直接系统调用write()到文件系统缓存,然后由操作系统决定何时刷盘fsync(),平衡性能和安全
controller service mapper层,mapper层不用mybatis,怎么设计不会修改太多service的代码
换成原生JDBC的话,同时想要Service层几乎不动,可以把Mapper层之上加一层Repository抽象,Service层依赖Repository而不是Mapper
高并发上传图片如何设计
保证高并发,数据可靠性
前端上传的时候【分块上传】,并发上传多块。
计算文件MD5,避免重复上传【秒传】
【断点续传】仅上传缺失分片,Range字段
服务端只负责【签名授权】,【客户端直接上传到OSS】,图片流不用经过Java服务
上传之后触发回调接口,告诉后端上传的【元信息】MySQL落库
图片元信息存在数据库中,结构是图片【唯一ID】、上传者、图片类型、存储路径【URL】、上传时间、状态
压缩、裁剪、缩略图的任务发送到消息中间件做【异步处理】
大量图片上传导致OOM怎么办
客户端直传图片到OSS,不经过Java服务
如果图片流确实要经过Java的话确实容易造成OOM,代码层面首先要杜绝调getBytes()方法,而是使用流式方式处理,比如ossClient.putObject(bucketName, objectName, in)就是自动流式处理。
SpringBoot中通过配置方式或者代码方式限制图片的大小
或者Spring也可以把上传的文件临时保存到磁盘而不是内存
项目中Redis挂了怎么办?
Redis挂了也要保证服务可用性,可以降级用本地缓存兜底,或者直接请求数据库,保证核心功能可用
微服务项目也可以触发限流或熔断,限流触发后通知上游服务429错误码不要立即重试,而是走兜底逻辑或者退避一段时间重试,熔断触发后也可以走兜底逻辑,避免故障扩散。
也可以将写失败的操作放到消息中间件里防止数据丢失
从架构方面来看,如果是哨兵模式,主节点挂了还有从节点顶上,集群模式会自动进行故障转移选出新的主节点
图结构数据如果用关系型数据库的二维表存储,怎么设计表的结构?
一张节点表,一张边表,节点表的一条数据代表一个节点,边表的一条记录代表两个节点的关系,包括起始节点ID、终点节点ID、边类型、边权重
平时用什么 JDK 版本做开发
JDK1.8,这是企业中最稳定,兼容性最好的版本,满足绝大部份需求,各种中间件兼容性也比较好,新版本虽然有一些语法糖,但是现有核心系统仍然以1.8为主,迁移成本比较大
JDK 17 的 Optional 类是干嘛的?
是一个容器类,用于存放一个可能为null的值,也可以说它是一个可以为空的盒子,里面要么有一个对象(非空),要么什么都没有(空盒子)。
主要是为了避免NullPointerException
Stream 流的原理
是一种基于pipeline流水线的模型,每个中间操作都会生成一个新的Stream流对象,底层是通过一连串的AbstractPipeline节点实现的,中间操作是惰性执行的,只有遇到终结操作,比如for each才触发执行,每个元素从数据源取出后,会顺序通过每个节点操作
Spring事务失效的场景
1. 方法不是public:AOP无法代理不是public的方法
2. 同类方法内调用:一个方法调用同类内另一个事务方法的时候,事务失效,因为代理对象在外部调用时才能生效,直接调用自身方法会绕过代理
Spring的事务传播行为有哪些?
REQUIRED:默认行为。如果当前存在事务,则加入;否则新建一个事务。
REQUIRES_NEW:总是新建一个事务,如果当前存在事务,则挂起当前事务。
SUPPORTS:如果当前存在事务,就加入,否则以普通方法执行
有一台 1GB 内存的机器和一个 1TB 大小的文件,文件的内容由空格、换行符、无序的数字组成,如何找到这个文件中最大的数字和最小的数字?
不能一次性读到内存,要用流式扫描处理,一边读一边计算,在遍历的过程中过滤非数字元素,并且维护最小值和最大值
让你来设计一个用户登录的流程,从安全和性能的角度考虑,你会怎么设计?
首先是功能需求:用户名/密码登录、邮箱/手机号登录、支持 MFA(TOTP/短信/Push)、记住我、登出、刷新 token、密码重置、设备管理。
然后是流程:
-
客户端(HTTPS)调用 POST /auth/login 提交 {identifier, password, deviceInfo}。identifier 可为 username/email/phone。
-
API Gateway 做安全检测(IP地址);通过则请求到 Auth Service。
-
查 DB 取用户实体,看看用户是否被禁用
-
使用安全哈希函数(Argon2 / bcrypt / PBKDF2-with-HMAC-SHA256)对比密码;
-
若密码错误:在Redis中增加失败计数(Redis 原子 INCR),达到阈值触发 CAPTCHA / 临时锁定 / notify;
-
客户端收到 token 存储在cookies
-
后续请求携带 Access Token(Header Authorization: Bearer … 或 Cookie)。验证通过即访问资源。
安全方面主要是密码加密存储和对比,强制 HTTPS 请求,利用图片验证码防止脚本攻击,设置用户黑明单,在网管层做安全检测IP地址
Token可以用JWT
Token的原理?
客户端提交用户名 + 密码,后台验证之后返回一个字符串,如果是JWT的话就是用密钥加密过的用户基本信息,避免频繁查询DB。
返回给客户端(前端 / App),客户端保存在Cookies或者LocalStorage
客户端每次请求时在 HTTP Header(通常是 Authorization: Bearer <token>)中携带;
服务端验证 Token 的合法性(签名、是否过期);
验证通过 → 放行请求;否则返回 401(未授权)。
怎么实现二维码登录?
服务端生成一个 随机的、唯一的登录标识 loginKey(UUID 或加密随机数)
根据这个随机的loginKey生成一个二维码,前端展示
loginKey 存储在服务端缓存(Redis)并设置过期时间(比如 2 分钟)
手机端扫码后获取 loginKey
手机端执行登录状态验证(用户已登录微信/账号)
手机端调用服务端接口确认登录:将 loginKey 标记为已确认,并关联当前用户 ID
PC 端定期轮询服务端接口查询 loginKey 是否已被确认
如果确认 → 生成 Access Token 或 Session,完成登录
如果二维码过期 → 提示用户刷新二维码
怎么设计一个分布式缓存系统
分布式缓存 = 分片 + 副本 + 缓存策略 + 一致性 + 高可用 + 可扩展设计
首先明确需求,缓存系统需要支持高速读写,支持key value存储,支持过期策略,支持高并发访问,支持高可用,数据一致性和故障恢复
缓存节点一定是集群部署的,可以是Redis集群
首先是数据分片,可以用一致性哈希减少节点扩展或者减少时的迁移成本,也可以Redis Cluster使用哈希槽数量16384来映射节点
每个缓存节点都要有主从副本,读写分离,主节点挂了可以启动从节点,保证高可用,从节点要保证和主节点的数据一致性(通过同步复制)。
还要设计淘汰策略和过期策略
Cache Aside模式保证缓存和数据库的一致性
现在用户登录功能,响应很慢,一直转圈圈,如何排查问题?
如果是后端的问题,看一下线上的日志有没有报错,也可以看一下慢查询日志,然后用EXPLATIN查看SQL的执行计划,看看是不是有filesort,缓存失效等问题。
缓存方面可以检查一下Redis是否命中?是不是由于大量请求打到DB导致的
服务器方面可以用top -Hp命令看一下CPU和内存占用率是否异常,然后用jstack生成堆栈信息文件,在这个文件用搜索CPU占用率高的线程的执行信息,最终定位到代码看看是不是有死循环,Redis和DB连接问题,或者死锁的问题
红黑树和b+树区别?
红黑树是高度自平衡的二叉搜索树,树高O(logn),b+树是多路平衡的搜索树树高比较低
红黑树每个节点都存储key value,b+树只有叶子结点有可能存储k-v,非叶子结点都存的是索引,节点具有天然有序性,方便范围查询
红黑树是二叉查找,每次访问节点都会触发IO,B+树由于比较矮,并且一个节点对应一个磁盘页,IO次数也比较少
红黑树的插入和删除操作是通过调整颜色和旋转保持平衡,B+树是通过分裂和合并节点维持多路平衡
MySQL索引应该怎么加
CREATE TABLE user (
id INT PRIMARY KEY, -- 主键索引
username VARCHAR(50) NOT NULL UNIQUE, -- 唯一索引
email VARCHAR(100),
age INT,
INDEX idx_age (age), -- 普通索引
INDEX idx_email_age (email, age) -- 复合索引
);
CREATE INDEX idx_username ON user(username);