在广州一个小公司(BOSS标注是0-20人,薪资2-3k),直接面试没有笔试,一开始就直接拿着简历问,也没有自我介绍,问题是结合场景题和八股文、基础。废话不多说,直接分享面试题目个大家做参考。
IOC就是控制反转,这是一种设计模式,核心思想是将对象的创建、依赖注入和生命周期管理交给IOC容器负责。在传统的编码方式中,我们一般需要在类中手动创建依赖对象,通过硬编码方式来控制对象的实例化和管理,而在Spring中,Bean以及对象之间的依赖关系都交给IOC容器负责,降低了代码之间的耦合度,也提高了系统的灵活性;
spring中主要通过XML配置和注解配置的两种方式实现。
数据库的三范式是关系型数据库设计的基本原则,旨在减少数据冗余、提高数据的一致性,并确保数据依赖的合理性;
第一范式:字段具有原子性,就是每个字段不能再拆分。比如联系方式应该拆分为电话和地址两个独立的字段;
第二范式:在满足第一范式的基础上,消除部分依赖,就是非主键字段必须完全依赖主键中的各个字段(表中使用的是复合主键,主键包含多个字段)。比如订单明细表的主键是订单ID+产品ID,字段客户姓名仅依赖订单ID,可以将客户姓名移到以订单ID为主键的订单表中;
第三范式:在满足第一、二范式的基础上,消除传递依赖,就是非主键字段不能依赖其他非主键字段。比如学生表包含学号(主键)、学院、学院电话,其中学院电话依赖学院,而学院依赖学号,应拆分为学生表(学号、学院)和学院表(学院、学院电话)
这种规则约束减少了冗余、避免了跟新异常,但过度范式化可能导致多表关联查询、降低性能;
(1)表设计优化
(2)索引优化:
(3)SQL语句优化:
索引创建原则和失效场景看这篇八股文:
https://blog.csdn.net/weixin_73144915/article/details/145535602?spm=1001.2014.3001.5501
多态指同一个操作作用于不同对象时,可以有不同的实现方式。核心目的是提高代码的灵活性和可扩展性;多态的两种形式如下:
1、编译时多态(静态多态),实现方法重载,在编译时根据参数类型和个数确定调用哪个方法;
2、运行时多态(动态多态),通过接口/继承和方法重写来实行,在运行时根据对象的实际类型决定调用哪个方法;
HashMap和Hashtable都是Java中用于存储键值对的哈希表类,区别如下:
(1)线程安全:HashMap不是线程安全的,如果多个线程并发访问HahsMap,并且至少有一个线程做了修改,他必须通过外部同步来保证线程安全,否则可能会导致数据不一致的情况;而Hashtable是线程安全的,他的方法都被synchronized修饰,可以在多线程环境下安全的被访问;
(2)性能:HashMap性能通常优于Hashtable,特别是在单线程环境下;而Hashtable由于方法上都有同步锁,性能较差;
(3)Null值:HashMap允许一个null值(键唯一性)和多个null值;而Hashtable不允许出现null键或null值。
SpringMVC的核心是基于前端控制器模式的请求驱动设计,将前端控制器作为中央调度器,拦截请求将请求分发给对应的处理器,并协调视图解析、数据绑定等组件完成全流程处理。其核心设计理念是解耦、模块化、可扩展。核心流程如下:
1.发送请求:用户发送请求,被前端控制器拦截;
2.映射处理器:处理器映射器根据URL找到对应的Controller层和方法;
3.调用控制器:Controller执行业务逻辑,返回ModelAndView数据;
4.解析视图:视图解析器将视图名称转化为具体视图(如HTML页面);
5.渲染视图:将模型数据填充到视图中,生成最终响应给用户;
解决超卖问题的核心在于保证库存扣减的原子性和一致性,尤其是在高并发场景下。以下是分层的解决方案,涵盖技术实现和业务逻辑优化:
一、技术层面解决方案
1. 数据库锁机制
悲观锁(Pessimistic Lock)
在事务中通过 SELECT ... FOR UPDATE
锁定库存记录,防止其他事务修改。
BEGIN;
SELECT stock FROM products WHERE id=1 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id=1;
COMMIT;
缺点:性能较差,适用于低并发场景。
乐观锁(Optimistic Lock)
通过版本号或时间戳控制并发,仅当库存未被修改时才扣减。
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id=1 AND version = {current_version} AND stock > 0;
优点:性能高,适合高并发;缺点:需重试逻辑(如失败后提示用户重新下单)。
2. 分布式锁(Redis/ZooKeeper)
使用 Redis 的 SETNX
或 RedLock 算法,确保同一时间只有一个请求能操作库存。
示例(Redis + Lua脚本保证原子性):
local key = "product_1_stock"
local decrement = 1
local stock = tonumber(redis.call('GET', key))
if stock >= decrement then
redis.call('DECRBY', key, decrement)
return 1 -- 扣减成功
else
return 0 -- 库存不足
end
适用场景:分布式系统,需配合数据库最终一致性。
3. 缓存预扣库存(Redis + 异步队列)
步骤:
将库存预热到 Redis 中。
用户下单时,先通过 DECR
扣减 Redis 库存。
若扣减成功,将订单信息发送到消息队列(如 Kafka、RabbitMQ),异步更新数据库。
若最终数据库更新失败,需回滚 Redis 库存(如通过 TTL 自动过期或补偿事务)。
优点:扛住瞬时高并发;缺点:需处理缓存与数据库的数据一致性。
配合着使用,一些简单的SQL语句可以直接用注解来写,而复杂SQL、联表查询的用XML来写
1. 采用 Token 进行身份验证
2. 避免前端传递用户 ID
userId
参数。@GetMapping("/user/profile")
public ResponseEntity getUserProfile(@RequestAttribute("userId") Long userId) {
UserProfile profile = userService.getProfileById(userId);
return ResponseEntity.ok(profile);
}
这里 userId
是从 JWT 解析出来的,而不是从前端传递的参数。
3. 进行权限校验
userId
,也要检查该 userId
是否与当前登录用户匹配。@GetMapping("/user/orders")
public ResponseEntity> getUserOrders(@RequestParam Long userId, @RequestAttribute("userId") Long currentUserId) {
if (!userId.equals(currentUserId)) {
throw new AccessDeniedException("非法访问");
}
return ResponseEntity.ok(orderService.getOrdersByUserId(userId));
}
这里 currentUserId
是从 Token 解析出来的,确保用户不能查询别人的订单。
4. 限制请求频率(防止暴力攻击)
Bucket4j
或 Guava RateLimiter
@RateLimiter(name = "user-api", fallbackMethod = "limitExceeded")
@GetMapping("/user/data")
public ResponseEntity> getUserData() {
return ResponseEntity.ok("数据获取成功");
}
public ResponseEntity limitExceeded(Exception e) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁,请稍后再试");
}
JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中,转化为JVM可识别的数据结构,从而使Java程序能够运行启动。核心作用如下:
(1)动态加载:在程序运行时按需要加载类,而非一次性加载所有类;
(2)隔离性:不同类加载器可以加载不同来源的类,避免命名冲突(如Tomcat隔离Web应用);
(3)安全性:通过双亲委派机制,防止恶意修改核心类库代码;