当前位置: 首页 > news >正文

JavaRedis和数据库相关面试题

JavaRedis面试题

1. Redis是什么以及Redis为什么快?

​ Redis(Remote Dictionary Server)是一个开源的内存键值数据库,支持多种数据结构(如字符串、哈希、列表、集合等),并提供持久化、复制、事务等功能。

Redis为什么快?

​ (1) 内存存储 + 单线程模型

​ 单线程优势:原子性操作无需加锁,避免线程切换和竟态条件。

因素说明
内存操作数据完全存储在内存中,读写速度远超磁盘(纳秒级访问)。
单线程架构避免多线程竞争锁和上下文切换开销(Redis 6.0 后引入多线程仅用于网络I/O)。

​ (2)高效的数据结构

​ Redis 针对不同场景优化了数据结构实现:

数据结构底层实现优化点
字符串(String)SDS(简单动态字符串)预分配内存、O(1) 时间复杂度获取长度。
哈希(Hash)哈希表 + 压缩列表(小数据)自动切换编码节省内存。
有序集合(ZSet)跳表(SkipList) + 哈希表范围查询高效(O(log N))。

​ (3) I/O 多路复用

​ 时间驱动模型:基于 epoll(Linux)或kqueue(Mac)实现高并发连接处理。

​ 非阻塞I/O:单线程监听多个套接字,就绪事件通过队列顺序处理。

​ (4) 协议与编码优化

​ RESP 协议:简单文本协议,解析效率高。

​ 紧凑数据存储:压缩列表、整数集合等节省内存。

​ (5) 避免慢操作

​ 异步持久化:RDB(快照)和 AOF(日志)尽量不阻塞主线程。

​ 管道(Pipeline):批量命令减少网络往返时间。

2.Redis可以用来做什么

1.缓存加速(Cache)

​ 场景:减轻数据库压力,加速热点数据访问。

​ 优势:内存读写速度是磁盘的 10万倍以上,支持过期时间自动淘汰(EXPIRE)。

2.会话存储(Session Storage)

​ 场景:分布式系统中的用户会话共享。

​ 优势:解决多服务器间 Session 不一致问题。 比数据库存储会话性能更高。

3.消息队列(Message Queue)

​ 场景:异步任务处理,解耦系统组件。

4.实时排行榜(Leaderboard)

​ 场景:游戏积分榜、热搜排名。

​ 优势:有序集合(ZSet)自动排序,时间复杂度O(log N)。

5.计数器(Counter)

​ 场景:文章阅读量、点赞数统计。

6.分布式锁(Distributed Lock)

​ 场景:防止多节点重复处理任务。

7.发布订阅(Pub/Sub)

​ 场景:实时通知(如聊天室、订单状态更新)

​ 缺点:消息不持久化,客户端断开后消失。

8.地理位置(Geospatial)

​ 场景:附近的人,店铺搜索。

3.Redis 持久化有几种方式?

Redis 提供两种持久化机制,确保数据在重启或故障后不丢失,两者可单独或组合使用。

1.RDB(Redis Database)

​ 原理:定时生成内存数据的二进制快照(Snapshot),保存为 .rdb 文件。

​ 触发方式:

​ 自动触发:按配置规则定时保存(默认策略)。

​ 手动触发:

​ SAVE:阻塞主线程,同步保存(生产环境慎用)。

​ BGSAVE:后台异步保存(通过 fork 子进程操作)。

​ 优点:文件紧凑,恢复速度快。 适合备份和灾难恢复。

​ 缺点:可能丢失最后一次快照的数据(取决于配置间隔)。大量数据时fork 子进程可能阻塞主线程(内存页复制开销)。

2.AOF(Append Only File)

​ 原理:记录所有写操作命令(文本格式),通过重放命令恢复数据。

​ 工作流程:

​ 1.写入命令追加到AOF缓冲区。

​ 2.根据策略同步到磁盘(appendfsync 配置):

​ always:每次写命令都同步(最安全,性能最低)。

​ everysec:每秒同步一次(推荐,平衡性能与安全)。

​ no:由操作系统决定(最快,可能丢失数据)。

​ 文件重写(AOF Rewrite):压缩AOF文件(移除冗余命令),通过BGREWRITEAOF触发或自动配置。

​ 优点:数据安全性高(最多丢失1秒数据)。可读性强(文本格式便于人工修复)。

​ 缺点:文件体积较大,恢复速度慢。高频写入时对磁盘压力大。

对比项RDBAOF
持久化方式定时快照记录所有写命令
数据安全性可能丢失最后一次快照后的数据根据配置最多丢失1秒数据
恢复速度快(直接加载二进制文件)慢(需重放命令)
文件大小小(二进制压缩)大(需定期重写优化)
适用场景允许少量数据丢失,追求快速恢复对数据安全性要求高

4.Redis 支持的数据类型有哪些?

1.String(字符串)

​ 特点:最基本类型,可存储文本、数字(≤512MB)或二进制数据。

​ 应用场景:缓存、计数器、分布式锁。

SET user:1001 "Alice"          # 存储字符串
GET user:1001                  # 获取值
INCR article:1001:views        # 数字自增
SETNX lock:order 1 EX 30       # 分布式锁(原子操作)

2.Hash(哈希表)

​ 特点:键值对集合,适合存储对象。

​ 应用场景:用户属性,商品详情。

HSET user:1001 name "Alice" age 25   # 设置字段
HGET user:1001 name                  # 获取字段
HGETALL user:1001                    # 获取所有字段

3.List(列表)

​ 特点:有序元素集合,支持双向操作。

​ 应用场景:消息队列、最新消息排行。

LPUSH news:latest "news1"       # 左侧插入
RPOP news:latest                # 右侧弹出
LRANGE news:latest 0 4          # 获取前5条

4.Set(集合)

​ 特点:无序且元素唯一,支持交并差运算。

​ 应用场景:标签系统,共同好友。

SADD tags:1001 "tech" "redis"   # 添加元素
SINTER tags:1001 tags:1002      # 求交集
SMEMBERS tags:1001              # 获取所有元素

5.ZSET(有序集合)

​ 特点:元素按分数排序,且唯一。

​ 应用场景:排行榜,优先级队列。

ZADD leaderboard 100 "PlayerA" 85 "PlayerB"  # 添加带分数元素
ZREVRANGE leaderboard 0 2 WITHSCORES         # 获取Top 3
ZRANK leaderboard "PlayerA"                  # 查询排名

6.Bitmap(位图)

​ 特点:通过位操作存储布尔值,极致节省空间。

​ 应用场景:用户签到、活跃统计。

SETBIT sign:2023:10 1001 1      # 用户1001在10月签到
BITCOUNT sign:2023:10           # 统计当月签到人数

7.HyperLogLog(基数统计)

​ 特点:估算集合不重复元素数量(误差 误差≤1%)。

​ 应用场景:UV(独立访客)统计。

PFADD uv:2023:10:01 "user1" "user2"  # 记录用户
PFCOUNT uv:2023:10:01                 # 估算UV

8.Stream(流)

​ 特点:消息队列,支持消费者和多播。

​ 应用场景:事件溯源、日志收集。

XADD orders * user 1001 item "book"   # 添加消息
XREAD COUNT 10 STREAMS orders 0       # 读取消息
XGROUP CREATE orders group1 0         # 创建消费者组
需求推荐类型原因
简单键值存储String直接读写,功能简单。
对象存储Hash字段独立操作,节省内存。
先进先出/后进先出队列List支持双向操作。
去重集合Set自动去重,支持集合运算。
带权重的唯一集合ZSet按分数排序,范围查询高效。
布尔值大量存储Bitmap极致节省空间(1亿用户仅需12MB)。
大数据量去重统计HyperLogLog固定12KB内存,误差可控。
消息队列Stream支持消费者组和历史消息回溯。

5.Redis 淘汰策略有哪些?

当 Redis 内存达到 maxmemory 限制时 (通过 maxmemory-policy 配置),会根据指定策略淘汰数据以释放空间。

1.不淘汰策略

策略行为适用场景
noeviction(默认)内存满时拒绝所有写入操作(返回OOM错误),读请求正常处理。数据绝对不允许丢失,且需人工干预的场景。

2.基于TTL的淘汰策略

策略行为特点
volatile-ttl设置了过期时间的键中,淘汰剩余时间最短的键。优先淘汰即将过期的数据,保留热点数据。

3.基于LRU(最近最少使用)的淘汰策略

策略行为特点
allkeys-lru所有键中淘汰最近最少使用的键。适合热点数据分布明显的场景(如缓存)。
volatile-lru设置了过期时间的键中淘汰最近最少使用的键。需手动设置过期时间,灵活性较低。

4.基于LFU(最不经常使用)的淘汰策略

策略行为特点
allkeys-lfu所有键中淘汰访问频率最低的键。适合长期运行且访问模式变化的场景。
volatile-lfu设置了过期时间的键中淘汰访问频率最低的键。需手动设置过期时间。

5.随机淘汰策略

策略行为特点
allkeys-random所有键中随机淘汰。简单但可能误删热点数据。
volatile-random设置了过期时间的键中随机淘汰。需手动设置过期时间。

6.Redis如何解决单机故障

Redis单击故障可能导致数据丢失或服务中断。

1.数据持久化(单机基础保障)

​ 原理:将内存数据保存到磁盘中,故障后恢复。

​ 方案:RDB快照,定时全量备份。 AOF日志:记录所有写操作。

​ 优点:简单易用,适合数据量小的场景。

​ 缺点:恢复事件较长,可能丢失部分数据。

2.主从复制(Replication)

​ 原理:主节点(Master)异步复制数据到从节点(Slave)。

​ 优点:读写分离,主节点写,从节点读。 故障时可手动切换从节点为主节点。

​ 缺点:异步复制可能导致数据丢失(主节点宕机时)。需人工干预故障转移。

​ 配置:

# 从节点配置文件
replicaof 192.168.1.100 6379  # 指定主节点IP和端口
replica-read-only yes         # 从节点只读

3.Redis Sentinel(哨兵)

​ 原理:哨兵集群监控主从节点,自动故障转移。

​ 架构:至少3个哨兵节点(避免脑裂)。哨兵通过投票机制选举新主节点。

​ 优点:自动故障检测与转移。客户端可通过哨兵获取新主节点地址。

​ 缺点:切换期间短暂不可用,从节点提升为主节点后需重配其他从节点。

​ 配置:

# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2  # 监控主节点,2表示需2个哨兵同意
sentinel down-after-milliseconds mymaster 5000  # 5秒无响应判定为主观下线

4.Redis Cluster(集群)

​ 原理:数据分片(16364个槽)存储在多节点,自动故障转移。

​ 架构:至少3主3从(6节点)。每个主节点对应一个从节点(备份)。

​ 优点:数据分片+高可用,支持水平扩展。自动故障转移(主节点宕机时从节点接替)。

​ 缺点:客户端需支持集群协议,跨槽操作需使用{} 强制路由。

​ 配置:

# 启动集群节点
redis-server --cluster-enabled yes --cluster-config-file nodes.conf# 创建集群
redis-cli --cluster create 192.168.1.100:6379 192.168.1.101:6379 ... --cluster-replicas 1

7.Redis 怎么实现分布式锁?

1.SETNX+EXPIRE

​ 原理:通过SETNX(Key不存在时设置)和 EXPIRE(设置过期时间)组合实现。

​ 风险:非原子操作,若SETNX后宕机,锁永不释放。

​ 实现:

SET lock:order 1 NX EX 30  # 原子化实现(Redis 2.6.12+)

2.唯一值验证(防误删)

​ 问题:锁可能被其他客户端误删。

​ 解决:为每个客户端设置唯一值(如UUID),删除时验证。

3.锁续期(WatchDog)

​ 问题:业务未完成但锁已过期。

​ 解决:后台线程定期续期(如每10秒检测一次)。

​ Java实现(Redission):

RLock lock = redisson.getLock("lock:order");
lock.lock();  // 默认30秒,看门狗自动续期
try {// 业务逻辑
} finally {lock.unlock();
}

4.Redlock 算法(多节点容错)

​ 场景:Redis集群环境,避免单点故障。

​ 流程:

​ 1.向N个独立Redis节点顺序请求加锁(SET NX EX)。

​ 2.当多数节点(≥ N/2 +1)加锁成功,且总耗时小于锁有效期,则成功。

​ 3.失败时向所有节点发送解锁请求。

​ Java实现:

Config config = new Config();
config.useClusterServers().addNodeAddress("redis://node1:6379", "redis://node2:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("lock:order");

5.分段锁 (提升并发)

​ 场景:高并发下减少锁粒度(如库存扣减)

​ 示例:

# 将库存拆分为10个分段
SET inventory:item1:seg1 100
SET inventory:item1:seg2 100
# 随机选择一个分段加锁
SETNX lock:item1:seg1 1
方案优点缺点适用场景
SETNX + EXPIRE简单非原子操作风险低要求临时方案
SET NX EX + Lua原子操作,防误删需处理续期中小规模应用
Redisson自动续期,可重入依赖Java客户端Java生态
Redlock高可用,防单点故障性能较低,实现复杂金融级关键业务
分段锁高并发业务逻辑复杂秒杀、库存扣减

8.Redis分布式锁有什么缺陷?

1.锁过期与业务执行冲突

​ 问题:锁自动过期后业务仍在执行,导致多客户端同时进入临界区。

​ 解决方案:锁续期(WatchDog)。合理设置超时:根据压测结果设置超时时间(如业务平均耗时 x 3)。

2.主从切换导致锁失效

​ 问题:主节点宕机时,未同步到从节点的锁丢失。

​ 解决方案:Redlock算法。使用Zookeeper/Etcd。

3.客户端阻塞导致锁失效

​ 问题:客户端因GC或网络阻塞未及时续期,锁过期后其他客户端获取锁。

​ 解决方案:心跳检测:客户端定期向Redis发送心跳。 熔断机制:检测到长时间阻塞主动释放锁。

4.锁误删(非持有者释放锁)

​ 问题:客户端A释放了客户端B的锁。

​ 解决方案:Lua脚本原子化验证。

5.锁不可重入

​ 问题:同一线程多次获取同一把锁导致死锁。

​ 解决方案:Redisson可重入锁。

6.锁竞争性能瓶颈

​ 问题:高并发下大量线程重试抢锁,导致CPU和网络压力。

​ 优化方案:分段锁。 排队队列。

9.什么是缓存穿透,如何解决?

缓存穿透是指查询一个数据库中不存在的数据。导致请求直接绕过缓存(未命中),每次都会访问数据库。这种情况通常由恶意攻击或业务逻辑缺陷引发。

​ 示例场景:

​ 用户请求一个不存在的商品ID(如 -1 或超大ID)。缓存无记录(未命中),直接查询数据库。数据库同样无结果,无法回填缓存,导致后续相同请求重复穿透。

​ 解决方案:

​ (1) 缓存空对象(Null Caching)

​ 原理:及时数据库查询为空,仍将空结果(如null)存入缓存,并设置较短的过期时间。

​ 优点:简单有效,适合大多数场景。

​ 缺点:内存浪费(需存储大量空值键)。短期数据不一致(如新增数据后仍需等待空缓存过期)。

​ (2) 布隆过滤器(Bloom Filter)

​ 原理:使用概率型数据结构预先存储所有合法Key,快速判断请求的Key是否存在。

​ 实现步骤:1.初始化布隆过滤器:将所有有效ID(如商品ID)存入过滤器。2.请求拦截:查询缓存前,先用布隆过滤器检查key是否存在。

​ 优点:内存占用极低(1亿元素仅需12MB,误判率1%)。拦截效率高(时间复杂度O(1))

​ 缺点:无法删除数据(需结合计数布隆过滤器或定期重建)。误判率存在。

​ (3) 接口层校验

​ 原理:在业务逻辑层或API网关对请求参数进行合法性校验。

​ 使用场景:ID必须为正整数。参数需符合特定格式(如手机号、邮箱)。

​ (4) 热点数据预加载

​ 原理:系统启动或定期任务预先加载所有有效Key到缓存

​ 使用场景:数据量可控(如身份场景、品类信息)。数据变更不频繁。

​ (5) 限流与熔断

​ 原理:对异常请求进行限流,防止数据库被击穿。

​ 工具:Redis计数器:限制同一Key的查询频率。Sentinel/Hystrix:触发熔断时返回降级效果。

10.什么是缓存击穿,如何解决?

缓存击穿是指某个热点Key在缓存中过期时,大量并发请求直接穿透到数据库,导致数据库瞬时压力激增。

​ 典型场景:

​ 明星离婚新闻的缓存Key过期,瞬间千万请求涌入数据库。

​ 解决方案:

​ (1) 互斥锁(Mutex Lock)

​ 原理:只允许一个线程重建缓存,其他线程等待或返回旧数据。

​ 优点:强一致性,避免重复查询。

​ 缺点:锁竞争可能增加延迟。

​ (2) 逻辑过期(Logical Expiration)

​ 原理:缓存永不过期,但存储包含时间戳的封装对象,由业务逻辑判断是否需异步更新。

​ 实现步骤:1.从缓存获取数据,检查是否逻辑过期。2.若过期,提交异步任务更新缓存,当前线程返回旧数据。

​ 优点:无锁设计,高并发性能好。

​ 缺点:短期数据不一致。

​ (3) 热点数据永不过期

​ 原理:对极热点Key设置较长的过期时间或后台定时更新。

​ 适用场景:高频访问且更新不频繁的数据(如首页推荐)。

​ 风险:需确保最终数据一致性。

​ (4) 多级缓存(分层缓存)

​ 架构:

​ L1(本地缓存):Caffeine/Guava Cache,过期时间短(如1分钟)。

​ L2(分布式缓存):Redis、过期时间长(如1小时)。

​ 请求流程:

​ 1.先查本地缓存,未命中则查Redis。

​ 2.Redis未命中时,由单个节点查数据库并回填,其他节点短暂等待。

​ 优点:减少Redis压力,避免集中击穿。

​ (5) 限流降级

​ 原理:缓存失效时,限制数据库查询的并发量。

​ 工具:Semaphore:控制并发线程数。Redis+Lua:分布式限流。

11.什么是缓存雪崩,如何解决?

缓存雪崩是指大量缓存Key在同一时间集中失效或Redis服务宕机,导致所有请求直接涌向数据库,引发数据库瞬时压力过载甚至崩溃的现象。

​ 典型场景:

​ 缓存中大量Key设置相同的过期时间(如午夜零点同时失效)。

​ Redis集群整体宕机,流量直接压垮数据库。

​ 解决方案:

​ (1) 差异化过期时间

​ 原理:为Key的过期时间添加随机值,避免同时失效。

​ 适用场景:批量预加载的缓存数据(如商品列表)。

​ (2) 永不过期 + 后台更新

​ 原理:缓存不设过期时间,通过异步任务定期更新。

​ 优点:彻底避免集中失效。

​ 缺点:需要维护额外的更新逻辑。

​ (3) 缓存预热

​ 原理:系统启动时预先加载热点数据到缓存。

​ 关键点:预热时需分散过期时间。

​ (4) Redis高可用部署

​ 架构方案:Redis Cluster:数据分片+多副本,单节点故障不影响整体。 Sentinel模式:主从切换自动故障转移。

​ 预防雪崩:确保集群节点分散在不同物理机/机柜。

12.Redis的哨兵模式工作原理是什么?

Redis 哨兵模式(Sentinel)用于实现高可用性,监控主从节点并在主节点故障时自动进行故障转移。

​ 1.工作流程:

​ 监控:哨兵节点定期检测主从节点是否存活(PING命令)。

​ 故障判定:多个哨兵确认主节点下线(主观下线+客观下线)。

​ 选举Leader:哨兵集群通过Raft算法选出一个Leader执行故障转移。

​ 故障转移:

​ 1.从节点中选出新主节点(基于优先级、复制偏移量)。

​ 2.通知其他从节点复制新主节点。

​ 3.通知客户端连接新主节点。

​ 2.客户端通知:通过发布订阅机制向客户端推送主节点变更信息。

​ 关键点:

​ 至少需要3个哨兵节点避免脑裂。

​ 不保证数据强一致性(原主节点恢复后成为从节点,可能丢失部分数据)。

Java数据库面试题

1.什么是事务?

事务(Transaction)是数据库操作的一个逻辑单元,它包含一组操作,这些操作要么全部成功,要么全部回滚。事务确保数据的一致性,即使在系统故障或并发访问时也能保证正确状态。

事务特性(ACID):

1.原子性:事务是不可分割的最小单元,要么全部执行,要么全部不执行。

2.一致性:事务执行的结果必须是数据库从一个一致性状态到另一个一致性状态。

3.隔离性:并发事务之间互补干扰。

4.持久性:事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。

2.mysql事务的隔离级别有哪些?

MySQL支持四种标准的事务隔离级别,用户控制并发事务之间的可见性和影响,从低到高依次为:

1.读未提交(Read Uncommitted

​ 事务可以读取其他事务未提交的修改。

​ 可能问题:脏读、不可重复读、幻读。

​ 性能最高,但数据一致性最差。

2.读已提交(Read Commited)

​ 事务只能读取其他事务已提交的修改。

​ 问题:不可重复读、幻读。

3.可重复读(Repeatable Read)

​ MySQL默认级别。

​ 事务内多次读取同一数据的结果一致。

​ 问题:可能发生幻读。

4.串行化(Serializable)

​ 最高隔离级别,所有事务串行执行,完全避免脏读、不可重复读、幻读问题。

​ 问题:性能最低。

3.不可重复读和幻读有什么区别?

不可重复读和幻读都是并发事务导致的一致性问题,但它们的表现和影响不同:

不可重复读:

​ 1.同一事务两次读取同一行数据,结果不同(数据被其他事务修改或删除)。

​ 2.针对已存在的行更新或删除。

幻读:

​ 1.同一事务内两次查询同一范围数据,结果集的行数不同(其他事务插入或删除了符合条件的数据)

​ 2.针对符合查询条件的新增行或删除行。

区别不可重复读幻读
现象同一事务内两次读取同一行数据,结果不同(数据被其他事务修改或删除)。同一事务内两次查询同一范围数据,结果集的行数不同(其他事务插入或删除了符合条件的数据)。
操作类型针对已存在的行的更新(Update)或删除(Delete)。针对符合查询条件的新增行(Insert)或删除行(Delete)。
示例事务A读取某用户年龄为20,事务B将年龄改为30并提交,事务A再次读取年龄变为30。事务A查询年龄>20的用户有5条,事务B插入一条年龄=25的记录并提交,事务A再次查询得到6条。
解决隔离级别Read Committed及以上可避免。Serializable或MySQL的Repeatable Read(通过间隙锁部分解决)。

4.spring实现事务的步骤

1.声明式事务(基于注解@Transactional)

步骤:

​ 1.启用事务管理

​ 在配置类添加@EnableTransactionManagement(Spring Boot默认已启用)。

​ 2.配置事务管理器

​ Spring Boot自动配置DataSourceTransactionManager,无需手动配置。

​ 3.在方法或类上添加@Transactional

@Service  
public class UserService {  @Transactional  public void transferMoney(Long fromId, Long toId, BigDecimal amount) {  // 数据库操作(如扣钱、加钱)  }  
}  

关键参数(可选):

​ isolation:隔离级别(如Isolation.READ_COMMITTED)。

​ propagation:传播行为(如Propagation.REQUIRED)。

​ rollbackFor:指定回滚的异常类型(默认回滚RuntimeException)。

5.MySQL存储金额用什么类型?

在MySQL中存储金额时,推荐使用 DECIMAL 类型。

类型特点适用场景
DECIMAL精确存储小数,避免浮点数精度丢失(如 DECIMAL(10,2) 表示总位数10,小数位2)金额、财务数据(必须精确)
FLOAT/DOUBLE近似存储,可能有精度问题(如 0.1 + 0.2 ≠ 0.3科学计算、非精确数据
INT/BIGINT将金额转为整数存储(如“分”为单位,100 表示 1.00 元)需要高性能计算的场景

​ DECIMAL示例:

CREATE TABLE orders (id INT PRIMARY KEY,amount DECIMAL(10, 2)  -- 最大存储 99999999.99
);-- 插入数据
INSERT INTO orders VALUES (1, 1234.56);

​ INT存储分(推荐优化方案)

CREATE TABLE orders (id INT PRIMARY KEY,amount BIGINT  -- 以分为单位,100 表示 1.00 元
);-- 插入数据
INSERT INTO orders VALUES (1, 123456);  -- 实际表示 1234.56 元

​ 选择建议:

​ 严格精确 -> DECIMAL:适合财政系统、交易系统,确保 0.01 元不会丢失。

​ 高性能需求 -> INT/BIGINT:以分为单位存储(如 1元=100),避免小数运算,提升计算速度(需在代码中处理单位转换)。

6.char和varchar的区别?

1.存储机制

​ char(10):

​ 存入 “abc” -> 存储为 ”abc "(补7个空格)

​ 始终占用10个字节(单字节字符集)或 10x字符集字节数(如 UTF=3字节/字符)。

​ varchar(10):

​ 存入“abc” -> 存储为 “3abc"(长度前缀 + 实际数据)

​ 占用实际长度 + 1字节(≤255)或2字节(>255)。

2.性能对比

场景CHARVARCHAR
读取速度⚡️ 更快(固定偏移量)⏳ 稍慢(需解析长度)
存储效率❌ 浪费空间(短数据)✅ 更节省空间
索引效率适合作为主键(定长优化)适合非主键字段

3.使用示例

CREATE TABLE users (id CHAR(36),          -- 固定长度(如UUID)username VARCHAR(50), -- 可变长度(如用户名)gender CHAR(1)        -- 固定长度(如'M'/'F')
);
-- CHAR(5) 存入 "hi" → 实际存储 "hi   "(补3空格)
INSERT INTO test VALUES ('hi', 'hi');-- VARCHAR(5) 存入 "hi" → 实际存储 "2hi"(长度+数据)
对比项CHARVARCHAR
存储方式固定长度,不足部分用空格填充可变长度,仅占用实际数据长度 + 长度标识(1-2字节)
存储空间固定分配(如 CHAR(10) 始终占10字符)动态分配(如 VARCHAR(10) 存 “abc” 占3+1=4字节)
检索速度更快(固定长度,无需计算偏移量)稍慢(需读取长度标识)
适用场景存储长度固定的数据(如MD5、UUID)存储长度变化的数据(如用户名、地址)
空格处理检索时会自动去除尾部填充的空格保留存储时的空格
最大长度255字符(MySQL)65535字节(受行大小限制)

7.where 和 having 的区别?

1.基本区别

​ where示例

-- 先筛选price>100的产品,再计算各类别的平均价格
SELECT category, AVG(price) as avg_price
FROM products
WHERE price > 100          -- 对原始数据过滤
GROUP BY category;

​ having示例

-- 先计算各类别平均价格,再筛选avg_price>200的类别
SELECT category, AVG(price) as avg_price
FROM products
GROUP BY category
HAVING avg_price > 200;    -- 对聚合结果过滤

2.关键使用场景

​ WHERE 适用场景

​ 过滤原始纪律(如日期范围、状态条件)

​ 需要利用索引提高查询效率时

​ 在连接查询中先过滤减少关联数据量

-- 查询2023年订单量超过5次的客户
SELECT customer_id, COUNT(*) as order_count
FROM orders
WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31'  -- 先按日期过滤
GROUP BY customer_id
HAVING COUNT(*) > 5;       -- 再筛选聚合结果

​ Having 适用场景

​ 基于聚合结果进行筛选(如总和、平均值、计数)

​ 需要对分组后的数据再过滤

-- 查询总销售额超过1万元的销售员
SELECT salesperson_id, SUM(amount) as total_sales
FROM sales
GROUP BY salesperson_id
HAVING SUM(amount) > 10000;  -- 筛选聚合结果
特性WHERE 子句HAVING 子句
执行时机分组前(GROUP BY) 过滤数据分组后(GROUP BY) 过滤结果
适用对象过滤 原始行数据过滤 分组后的聚合结果
聚合函数不能直接使用聚合函数作为条件可以使用聚合函数作为条件
性能通常更高效(先过滤后分组)相对低效(先分组后过滤)
索引使用可以使用索引优化不能使用索引(因为操作的是分组后结果)

8.select *和select 全部字段有什么区别?

1.性能差异

​ 数据库层面:

​ select *:

​ 需要读取所有列数据(包括不需要的列)

​ 无法利用覆盖索引(Covering Index)

​ SELECT 列名:

​ 只读取指定列数据

​ 当查询的列都包含在索引中时,可以直接从索引获取数据(不需要回表)

-- 假设有索引 idx_name_age (name, age)
-- 低效写法(需要回表查所有字段)
SELECT * FROM users WHERE name = '张三';-- 高效写法(使用覆盖索引)
SELECT name, age FROM users WHERE name = '张三';

2.开发问题

​ (1) 表结构变更风险

-- 原始表结构:users(id, name, age)
SELECT * FROM users;  -- 返回 id, name, age-- 新增字段后:users(id, name, age, address, phone)
-- 同样的查询现在会返回更多字段,可能导致:
-- 1. 应用程序解析错误
-- 2. 数据传输量意外增加

​ (2) JOIN查询字段冲突

-- 两个表都有id字段时
SELECT * FROM users JOIN orders ON users.id = orders.user_id;
-- 结果集中会出现两个id字段,应用程序可能无法正确解析-- 正确写法
SELECT users.id AS user_id, users.name,orders.id AS order_id,orders.amount
FROM users 
JOIN orders ON users.id = orders.user_id;
对比维度SELECT *SELECT 全部字段
可读性❌ 差(无法直观看到返回的字段)✅ 好(明确列出所有字段)
性能影响⚠️ 可能较差(特别是表结构变更时)✅ 更优(只传输需要的字段)
表结构变更适应性❌ 差(新增字段会自动返回)✅ 好(明确字段列表不受影响)
索引利用❌ 可能无法有效利用索引✅ 可以针对性优化
网络传输❌ 传输不必要的数据✅ 只传输需要的字段
JOIN查询❌ 容易产生字段冲突✅ 可以明确指定来源表
存储过程/视图❌ 维护困难✅ 更容易维护

9.mysql是怎么完成分页的?

MySQL主要通过 LIMIT 字句实现分页查询。

1.基础分页语法

​ offset:跳过多少条记录(从0开始)

​ page_size:每页显示多少条记录

SELECT * FROM table_name 
LIMIT offset, page_size;

​ 示例:查询第二页,每页10条

-- 偏移量 = (页码 - 1) * 每页大小
SELECT * FROM users 
ORDER BY create_time DESC 
LIMIT 10, 10;  -- 第2页(跳过前10条,取10条)-- 偏移量 = (页码 - 1) * 每页大小
SELECT * FROM users 
ORDER BY create_time DESC 
LIMIT 10, 10;  -- 第2页(跳过前10条,取10条)

10.索引的类型有哪些?

索引是数据库优化查询性能的核心机制,不同数据库系统支持的索引类型各有特点。

1.按数据结构分类

​ (1) B-Tree索引(平衡树索引)

​ 适用场景:等值查询、返回查询、排序操作

​ 支持数据库:MySQL(InnoDB)、PostgreSQL、Oracle、SQL Server

​ 特点:数据有序存储(支持ORDER BY优化),适合高基数(唯一值多)列,时间复杂度O(log n)

​ (2) Hash索引

​ 适用场景:精确匹配查询(不支持范围查询)

​ 支持数据库:MySQL(Memory引擎)、Redis

​ 特点:查询时间复杂度 O(1),不支持部分键查询和排序,易发生哈希冲突。

​ (3) 全文索引(Full-Text)

​ 适用场景:文本内容的关键词搜索。

​ 支持数据库:MySQL、PostgreSQL、Elasticsearch(专业全文检索引擎)

​ 特点:支持自然语言搜索和布尔搜索,使用倒排索引实现。

​ (4)R-Tree索引(空间索引)

​ 适用场景:地理空间数据(GIS)

​ 支持数据库:MySQL、PostgreSQL(PostGIS)、Oracle

​ (5) Bitmap索引

​ 适用场景:低基数(唯一值少)的列(如性别、状态)

​ 支持数据库:Oracle、SQL Server

​ 特点:存储效率极高,适合数据仓库场景,锁粒度大,不适合高并发写入。

2.按物理存储分类

​ (1) 聚簇索引(Clustered Index)

​ 特点:

​ 索引与数据行物理存储顺序一致(InnoDB的主键索引)

​ 一个表只能有一个聚簇索引

​ 优势:

​ 范围查询效率高,减少随机I/O。

​ (2) 非聚簇索引(Secondary Index)

​ 特点:

​ 索引与数据分离存储(如MyISAM的所有索引)

​ 需要回表查询

3.按逻辑功能分类

​ (1) 主键索引(PRIMARY KEY)

​ 特点:

​ 唯一且非空。

​ InnoDB中自动成为聚簇索引。

CREATE TABLE users (id INT PRIMARY KEY,  -- 主键索引name VARCHAR(50)
);

​ (2) 唯一索引(UNIQUE)

​ 特点:

​ 保证列值唯一(允许NULL值)

​ 常用于业务唯一约束

CREATE UNIQUE INDEX idx_email ON users(email);

​ (3) 普通索引 (INDEX)

​ 特点:

​ 最基本的索引类型

​ 无唯一性约束

CREATE INDEX idx_name ON users(name);

​ (4) 复合索引(联合索引)

​ 特点:

​ 多列组合的索引

​ 遵循最左前缀法则

CREATE INDEX idx_name_age ON users(last_name, age);
-- 有效查询
SELECT * FROM users WHERE last_name = 'Smith';
SELECT * FROM users WHERE last_name = 'Smith' AND age = 30;
-- 无效查询(未使用最左列)
SELECT * FROM users WHERE age = 30;

​ (5) 覆盖索引(Covering Index)

​ 特点:

​ 索引包含查询所需的所有字段

​ 避免回表查询

CREATE INDEX idx_cover ON users(id, name);
-- 覆盖索引查询
SELECT id, name FROM users WHERE id = 100;

4.特殊类型索引

​ (1) 函数索引

​ 适用场景:对列进行函数计算后的查询

​ 支持数据库:Oracle、PostgreSQL、MySQL 8.0+

-- MySQL 8.0+
CREATE INDEX idx_year ON users((YEAR(birth_date)));

​ (2) 部分索引(Partial Index)

​ 适用场景:只为部分数据创建索引

​ 支持数据库:PostgreSQL、SQL Server

11.MySQL索引底层原理

MySQL索引的核心目的是加速数据检索,其底层实现因存储引擎而异。

1.核心数据结构

​ (1) B+树与B树的对比

特性B+树B树
数据存储位置仅叶子节点存储数据所有节点都可能存储数据
叶子节点链接通过指针形成有序链表
查询稳定性任何查询都需要到叶子层可能在非叶子节点命中
范围查询效率极高(链表遍历)需要中序遍历

​ (2) InnoDB的B+树特点

​ 非叶子节点:存储键值+子节点指针(6字节)

​ 叶子节点:存储完整数据行(聚簇索引),存储主键值(二级索引)

2.聚簇索引(Clustered Index)

​ (1) 存储规则

​ InnoDB必须有且只有一个聚簇索引

​ (2)物理存储方式

​ 叶子节点直接存储完整数据行数据(称为“索引组织表”)

​ 数据按主键顺序物理存储(插入时可能导致页分裂)

​ (3)优势

​ 范围查询效率高(如 where id >100)

	避免回表(覆盖索引场景)

3.二级索引(Secondary Index)

​ (1) 存储结构

​ 叶子节点存储:索引列值 + 主键值

​ 查询需要回表:通过主键回到聚簇索引查询完整数据

​ (2) 回表示例

​ 查询流程:

​ 1.在 idx_name B+树种找到 name = ‘Alice’ 的叶子节点

​ 2.获取对应的主键值(如 id =123)

​ 3.用 id = 123 到聚簇索引查询完整行数据

-- 假设有索引 idx_name(name)
SELECT * FROM users WHERE name = 'Alice';

​ (3) 覆盖索引优化

-- 只查询索引列可避免回表
SELECT id, name FROM users WHERE name = 'Alice';

4.InnoDB与MyISAM索引对比

特性InnoDBMyISAM
索引类型聚簇索引+二级索引非聚簇索引(索引与数据分离)
数据存储主键索引包含完整数据索引仅存储数据文件指针
并发控制行级锁表级锁
事务支持支持不支持

12.聚簇索引与非聚簇索引有什么区别

聚簇索引(Clustered Index)

​ 定义与特点

​ 数据即索引:表数据按照聚集索引的键值物理排序存储

​ 唯一性:每个表只能有一个聚集索引(InnoDB中通常是主键)

​ 存储结构:B+树的叶子节点直接包含完整的数据行

​ 查询优势:通过聚集索引访问数据非常高效,特别是范围查询

​ InnoDB的实现:

​ 如果定义了主键,则主键就是聚集索引

​ 如果没有主键,则选择第一个非空的唯一索引作为聚集索引

​ 如果都没有,InnoDB会隐式创建一个6字节的ROWID作为聚集索引

-- InnoDB表,id是聚集索引
CREATE TABLE users (id INT PRIMARY KEY,  -- 聚集索引name VARCHAR(50),age INT
);

非聚簇索引(Non-clustered Index)

​ 定义与特点

​ 独立结构:索引结构与数据存储分离

​ 多索引支持:一个表可以有多个非聚集索引

​ 存储结构:B+树的叶子节点存储的是指向数据行的指针(主键值或行指针)

​ 查询过程:需要"回表"操作,先查索引再查数据

​ 实现方式:

​ InnoDB:二级索引叶子节点存储主键值,需要回表查询

​ MyISAM:存储数据行的物理地址(文件偏移量)

-- name列上的非聚集索引
CREATE INDEX idx_name ON users(name);
特性聚簇索引(Clustered Index)非聚簇索引(Non-Clustered Index)
数据存储方式索引与数据行物理存储在一起索引与数据分离存储
叶子节点内容存储完整数据行存储主键值或数据行指针
数量限制每表仅能有一个每表可创建多个(MySQL默认16个)
查询效率主键查询极快需要回表查询
插入性能影响页分裂可能影响性能影响相对较小
典型实现引擎InnoDBMyISAM、MongoDB等

13.MySQL的回表查询原理是什么?

1.什么是回表查询?

​ 回表查询是指在使用非聚簇索引(二级索引)进行查询时,MySQL需要二次访问聚簇索引才能获取完整数据的过程。这种查询方式比直接使用聚簇索引多一次I/O操作,是常见的性能瓶颈来源

2.回表查询的核心原理

​ (1) InnoDB索引结构对比

索引类型叶子节点存储内容查询特点
聚簇索引完整数据行直接获取数据,无需回表
二级索引索引列值 + 主键值需通过主键回表查完整数据

​ (2) 回表流程示意图

步骤1: 查找索引
步骤2: 回表查询
步骤3: 获取数据
二级索引B+树
获取主键值
聚簇索引B+树
完整数据行

3.回表查询示例

-- 表结构
CREATE TABLE `users` (`id` INT PRIMARY KEY,`name` VARCHAR(50),`age` INT,INDEX `idx_name` (`name`)
);-- 触发回表的查询
SELECT * FROM users WHERE name = 'Alice';

​ 执行流程:

​ 1.通过 idx_name 索引找到 name = ‘Alice’ 的叶子节点

​ 2.获取对应的主键值(如 id = 101)

​ 3. 用 id=101 到聚簇索引种查找完整行数据

​ (2) 分页查询种的回表陷阱

​ 问题:先通过 idx_name 排序获取 1010 条主键,再回表查询1010次完整数据,最后丢弃前1000条。

-- 深度分页导致大量回表
SELECT * FROM users ORDER BY name LIMIT 100000, 10;

4.如何避免回表查询

​ (1) 使用覆盖索引

​ 关键点:索引包含所有查询字段

-- 创建覆盖索引
CREATE INDEX idx_cover ON users(name, age);-- 优化后的查询(无需回表)
SELECT name, age FROM users WHERE name = 'Alice';

​ (2) 强制使用聚簇索引

-- 对于主键查询不会回表
SELECT * FROM users WHERE id = 101;-- 使用USE INDEX提示
SELECT * FROM users USE INDEX(PRIMARY) WHERE name = 'Alice';

14.索引失效的情况有哪些?

1.违反最左前缀原则

​ 解决方案:

​ 确保查询条件包含最左列

​ 调整索引顺序或创建新索引

-- 联合索引: INDEX(col1, col2, col3)
SELECT * FROM table WHERE col2 = 'value'; -- 失效
SELECT * FROM table WHERE col3 = 'value'; -- 失效

2.对索引列使用函数或运算

​ 失效场景:

-- 索引: INDEX(create_time)
SELECT * FROM users WHERE YEAR(create_time) = 2023; -- 失效
SELECT * FROM products WHERE price + 10 > 100;      -- 失效

​ 解决方案:

​ 使用范围查询替代函数:

SELECT * FROM users 
WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';

​ MySQL8.0+可使用函数索引

CREATE INDEX idx_year ON users((YEAR(create_time)));

3.隐式类型转换

​ 失效场景:

-- 索引: INDEX(phone) phone是varchar类型
SELECT * FROM users WHERE phone = 13800138000; -- 失效(数字转字符串)

​ 解决方案:

​ 保持类型一致

SELECT * FROM users WHERE phone = '13800138000';

4.使用前导通配符LIKE

​ 失效场景:

-- 索引: INDEX(name)
SELECT * FROM users WHERE name LIKE '%Alice%'; -- 失效
SELECT * FROM users WHERE name LIKE '%Alice';  -- 失效

​ 解决方案:

​ 尽量使用后置通配符:

SELECT * FROM users WHERE name LIKE 'Alice%'; -- 使用索引

5.OR连接非索引列

​ 失效场景:

-- 索引: INDEX(age)
SELECT * FROM users WHERE age = 20 OR name = 'Alice'; -- name无索引则全表扫描

​ 解决方案:

​ 为所有OR条件列建索引

​ 改用UNION ALL:

SELECT * FROM users WHERE age = 20
UNION ALL
SELECT * FROM users WHERE name = 'Alice';

6.使用NOT、!=、<>操作符

​ 失效场景:

-- 索引: INDEX(status)
SELECT * FROM orders WHERE status != 'completed'; -- 可能失效

​ 解决方案:

​ 改写为范围查询:

SELECT * FROM orders WHERE status < 'completed' OR status > 'completed';

7.索引列参与计算

​ 失效场景:

-- 索引: INDEX(age)
SELECT * FROM users WHERE age + 1 > 20; -- 失效

​ 解决方案:

​ 将计算移到等式另一边:

SELECT * FROM users WHERE age > 19;

8.使用IS NULL/IS NOT NULL

​ 失效场景:

-- 索引: INDEX(name)
SELECT * FROM users WHERE name IS NULL; -- 可能失效

​ 解决方案:

-- 添加默认值替代NULL
ALTER TABLE users MODIFY name VARCHAR(50) DEFAULT '' NOT NULL;

9.数据量过少时优化器放弃索引

​ 失效场景:

-- 表只有100行数据时
SELECT * FROM small_table WHERE indexed_col = 'value'; -- 可能全表扫描

​ 解决方案:

​ 使用FORCE INDEX提示:

SELECT * FROM small_table FORCE INDEX(idx_col) WHERE indexed_col = 'value';

10.索引列顺序与排序方向不一致

​ 失效场景:

-- 索引: INDEX(col1 ASC, col2 DESC)
SELECT * FROM table ORDER BY col1 DESC, col2 ASC; -- 排序失效

​ 解决方案:

​ 创建匹配的索引:

CREATE INDEX idx_mixed ON table(col1 DESC, col2 ASC);

15.什么是最左匹配原则?

最左匹配原则:在使用联合索引(复合索引)时,MySQL会从左到右依次匹配索引列,只有查询条件包含联合索引的最左列,索引才会被使用。

关键特性

​ 前缀匹配:类似于字典的字符排序查找

​ 中断停止:若中间某列未在查询中出现,则其后的索引列不生效。

​ 顺序敏感:索引列的顺序决定索引的有效性。

联合索引的结构原理

​ 索引存储方式 INDEX(a,b,c),索引在B+树种的排列顺序为:

​ 1.先按 a 列排序

​ 2.a 相同则按 b 排序。

​ 3.a 和 b 都相同再按 c 排序。

a=1
b=2
b=3
c=4
c=5

​ (2) 索引查找过程

​ 有效查找: a -> b -> c (从左到右)

​ 无效查找: 直接查找 b 或 c (无法利用索引有序性)

16.SQL优化方案

1.索引优化

​ 合理创建索引:在 WHERE、JOIN、ORDER BY 和 GROUP BY 涉及的列上创建索引

​ 联合索引设计:遵循最左前缀原则,将高频查询和高选择性列放在前面

​ 避免冗余索引:删除不再使用或重复的索引

​ 覆盖索引:让索引包含查询所需的所有字段,避免回表操作

2.查询语句优化

​ 避免SELECT * :只查询需要的列

​ 合理使用JOIN:小表驱动大表,确保JOIN字段有索引

​ 优化子查询:能用 JOIN 代替的子查询尽量用 JOIN

​ 避免全表扫描:确保 WHERE 条件能使用索引

3.数据库设计优化

​ 合理的数据类型:使用最小满足需求的数据类型

​ 分区表:大数据表按时间、范围等维度分区

​ 垂直拆分:将不常用的大字段拆分到单独表

4.执行计划分析

​ 使用EXPLAIN:分析查询执行计划,找出性能瓶颈

​ 识别全表扫描:避免出现 ALL 类型的访问方式

5.高级优化技巧

​ 使用临时表:复杂查询可拆为多个简单查询

​ 读写分离:查询走从库,减轻主库压力。

​ 缓存策略:合理使用应用层缓存和数据库缓存。

JavaMyBatis面试题

1.ResultType 和 ResultMap 的区别是什么?

ReusltType:

​ 自动将查询结果的列名与Java对象的属性名匹配(忽略大小写)。

​ 要求数据库字段名和java属性名完全一致(如user_name -> userName 需开启驼峰映射)

<select id="getUser" resultType="com.example.User">SELECT id, user_name, age FROM user
</select>

ResultMap:

​ 显示定义字段与属性的映射关系,支持负载场景(如一对多、类型转化)

<resultMap id="userMap" type="com.example.User"><id property="id" column="id"/><result property="name" column="user_name"/><result property="age" column="age"/>
</resultMap><select id="getUser" resultMap="userMap">SELECT id, user_name, age FROM user
</select>
对比项ResultTypeResultMap
作用直接指定返回结果的Java类型(自动映射)。自定义字段与属性的映射关系(复杂映射场景)。
灵活性低(要求数据库字段与Java属性名严格一致)。高(支持字段别名、嵌套对象、集合映射等)。

2.MyBatis中 #{} 和 ${} 的区别是什么?

#{}:MyBatis 会将其转换为 PreparedStatement 的参数占位符 ?,通过JDBC 预编译执行。

-- 最终SQL(安全)
SELECT * FROM user WHERE id = ?

${}:直接拼接到字符串到SQL中,不会预编译。。

-- 最终SQL(危险!)
SELECT * FROM user WHERE id = 1001

SQL注入风险:

${}的风险示例:

​ 若参数值为 “1 OR 1=1”,拼接后SQL变为:

SELECT * FROM user WHERE id = 1 OR 1=1  -- 查询所有数据!

#{}的防护:

​ 即使参数值为 “1 OR 1=1”,实际执行的是:

SELECT * FROM user WHERE id = '1 OR 1=1'  -- 作为普通字符串处理
对比项#{}(预编译占位符)${}(字符串拼接)
处理方式生成预编译SQL(? 占位符),防止SQL注入。直接替换为参数值,存在SQL注入风险
安全性安全不安全
适用场景动态参数值(如 WHERE id = #{id})。动态表名、列名(如 ORDER BY ${column})。
数据类型转换自动根据Java类型转换(如日期格式化)。原样替换,不处理类型转换
性能预编译SQL可复用,性能更优。每次生成新SQL,性能略低。

3.MyBatis动态标签都有哪些?

1.基础条件控制标签

标签作用示例
<if>条件判断,满足条件时包含 SQL 片段。xml <if test="name != null"> AND name = #{name} </if>
<choose>多条件选择(类似 Java 的 switch-case)。xml <choose> <when test="id != null">AND id = #{id}</when> <otherwise>AND status = 1</otherwise> </choose>
<when><choose> 中使用,表示一个条件分支。见上例
<otherwise><choose> 中使用,表示默认分支。见上例

2.循环遍历标签

标签作用示例
<foreach>遍历集合(如 IN 查询、批量插入)。xml <foreach item="item" collection="list" open="(" separator="," close=")"> #{item} </foreach>
参数说明
collection集合参数名(如 listarray@Param("ids") 指定的名称)。
item当前元素的变量名。
open/close循环开始/结束时添加的字符串(如括号)。
separator元素间的分隔符(如逗号)。

3.字符串处理标签

标签作用示例
<trim>自定义字符串修剪(去除多余前缀/后缀)。`xml <trim prefix=“WHERE” prefixOverrides="AND
<where>自动处理 WHERE 关键字,去除开头多余的 AND/ORxml <where> <if test="name != null">AND name = #{name}</if> </where>
<set>自动处理 SET 关键字,去除结尾多余的逗号。xml <set> <if test="name != null">name = #{name},</if> </set>

4.特殊用途标签

标签作用示例
<bind>创建变量并绑定到上下文,用于模糊查询或复杂表达式。xml <bind name="pattern" value="'%' + name + '%'" /> SELECT * FROM user WHERE name LIKE #{pattern}
<sql>定义可重用的 SQL 片段。xml <sql id="userColumns">id, name, age</sql> SELECT <include refid="userColumns"/> FROM user
<include>引用 <sql> 定义的片段。见上例

4.当实体类的属性名和表中的字段名不一致时如何处理?

当Java实体类的属性名与数据库表的字段名不一致时,MyBatis无法自动映射数据。

1.使用Result 注解(注解方式)

​ 适用场景:简单字段映射,无需修改SQL。

public interface UserMapper {@Results({@Result(property = "userId", column = "user_id"),  // 属性名 → 字段名@Result(property = "userName", column = "username")})@Select("SELECT * FROM t_user")List<User> getAllUsers();
}

2.定义 resultMap>(XML方式)

​ 适用场景:复杂映射或需要复用配置。

<!--Mapper XML中定义 -->
<resultMap id="userMap" type="User"><id property="userId" column="user_id"/><result property="userName" column="username"/><result property="createTime" column="create_time"/>
</resultMap><!-- 引用resultMap -->
<select id="getUserById" resultMap="userMap">SELECT * FROM t_user WHERE user_id = #{id}
</select>

3.开启驼峰命名自动映射

​ 适用场景:字段名木盒下划线驼峰规则(如 user_name -> userName)。

​ 配置:在 mybatis-config.xml 或 SpringBoot 配置文件中启用:

# application.yml(Spring Boot)
mybatis:configuration:map-underscore-to-camel-case: true
<!-- mybatis-config.xml -->
<settings><setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

4.SQL 别名(直接修改查询语句)

​ 适用场景:临时调整或简单查询。

<select id="getUser" resultType="User">SELECT user_id AS userId, username AS userName FROM t_user
</select>

5.在mapper中如何传递多个参数?

1.使用 @Param 注解(推荐)

​ 使用场景:方法参数较少(≤3个),代码可读性高。

public interface UserMapper {// 通过@Param指定参数名,XML中直接使用List<User> selectByCondition(@Param("name") String name,@Param("age") Integer age,@Param("status") Integer status);
}

​ XML中使用:

<select id="selectByCondition" resultType="User">SELECT * FROM user WHERE name = #{name}AND age = #{age}AND status = #{status}
</select>

2.通过 Map 传递参数

​ 适用场景:动态参数或参数数量不确定时。

​ 缺点:参数名需硬编码,类型不安全。

List<User> selectByMap(Map<String, Object> params);
Map<String, Object> params = new HashMap<>();
params.put("name", "Alice");
params.put("age", 25);
userMapper.selectByMap(params);

​ XML中使用:

<select id="selectByMap" resultType="User">SELECT * FROM user WHERE name = #{name}AND age = #{age}
</select>

3.使用 JavaBean 对象封装参数

​ 适用场景:参数逻辑相关且需要复用。

​ 优点:类型安全,易于扩展。

// 定义参数对象
@Data
public class UserQuery {private String name;private Integer age;private Integer status;
}// Mapper接口
List<User> selectByBean(UserQuery query);

​ XML中使用:

<select id="selectByBean" resultType="User" parameterType="com.example.UserQuery">SELECT * FROM user WHERE name = #{name}AND age = #{age}AND status = #{status}
</select>

4.混合使用 @Param 和 JavaBean

​ 适用场景:部分参数需要复用,部分参数为临时参数。

List<User> selectByMixed(@Param("query") UserQuery query,@Param("role") String role
);

​ XML中使用:

<select id="selectByMixed" resultType="User">SELECT * FROM user WHERE name = #{query.name}AND age = #{query.age}AND role = #{role}
</select>
方式优点缺点适用场景
@Param 注解简单直观,类型安全参数较多时方法签名冗长参数少(≤3个)
Map 传递灵活,适合动态参数需硬编码key,类型不安全参数名或数量不确定时
JavaBean 封装类型安全,易于扩展需额外定义类参数逻辑相关且需复用
混合使用兼顾灵活性和复用性复杂度较高部分参数需复用的场景

6.MyBatis分页插件的原理

MyBatis分页插件(如PageHelper)的核心原理是通过拦截器(interceptor)动态修改SQL语句实现物理分页。

核心实现原理

​ 拦截器机制

​ 拦截目标:StatementHandler.prepare() 方法

​ 触发时机:在SQL执行前拦截并改写

@Intercepts({@Signature(type= StatementHandler.class, method="prepare", args={Connection.class, Integer.class})
})
public class PageInterceptor implements Interceptor {// 拦截SQL执行流程
}

​ 分页流程

​ 1.检测是否需要分页:通过ThreadLocal保存分页参数(页码、每页条数)

PageHelper.startPage(1, 10); // 设置分页参数

​ 2.改写原始SQL

​ MySQL:添加 LIMIT offset,size

-- 原始SQL
SELECT * FROM user;-- 改写后
SELECT * FROM user LIMIT 0, 10;

​ 3.执行修改后的SQL

​ 通过反射调用原 StatementHandler 继续执行

​ 4.获取总记录数(可选)

​ 执行COUNT(*) 查询获取总数。

相关文章:

JavaRedis和数据库相关面试题

JavaRedis面试题 1. Redis是什么以及Redis为什么快&#xff1f; ​ Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存键值数据库&#xff0c;支持多种数据结构&#xff08;如字符串、哈希、列表、集合等&#xff09;&#xff0c;并提供持久化、复制、…...

Android开发RxJava3延迟操作

Android开发RxJava3延迟操作 直接上代码&#xff1a; /*** param timeMillis 毫秒单位* desc : 延迟多少毫秒操作,* 注&#xff1a;它和Activity生命周期绑定&#xff0c;界面关闭了不会再执行delayTodoListener.delayTodo()* author : congge on 2021-03-25 15:31**/p…...

android 设置状态栏背景

一 让activity ui界面和手机状态栏一样的背景 要让 Activity 的 UI 界面和手机状态栏具有相同的背景颜色&#xff0c;并且能够随着深色模式和非深色模式的切换而改变颜色&#xff0c;你可以按照以下步骤操作&#xff1a; 1. 让 Activity 和 状态栏背景颜色一致 使用 window.s…...

vue 常见优化手段

文章目录 vue常见的优化手段前言使用key(避免明明相同的dom,每次更新都要重新生成)使用冻结的对象(避免无意义的响应式数据)使用函数式组件(减少vue组件实例的生成)vue3vue2使用计算属性(减少数据计算的次数)非实时绑定的表单项(避免表单过多触发监听事件)保持对象的…...

vue生命周期、钩子以及跨域问题简介

Vue 的生命周期是指 Vue 实例从创建到销毁的整个过程。在这个过程中&#xff0c;Vue 提供了一系列的生命周期钩子&#xff08;Lifecycle Hooks&#xff09;&#xff0c;允许开发者在特定的时间点执行代码。以下是 Vue 的生命周期和钩子的简单说明&#xff1a; Vue 的生命周期阶…...

主相机绑定小地图

资源初始化&#xff1a;在类中通过 property 装饰器定义主相机、小地图相机、小地图精灵等资源属性&#xff0c;便于在编辑器中赋值。在 start 方法里&#xff0c;当确认这些资源存在后&#xff0c;创建渲染纹理并设置其大小&#xff0c;将渲染纹理与小地图相机关联&#xff0c…...

关于音频采样率,比特,时间轴的理解

是的&#xff0c;你的理解完全正确&#xff01;-ar、-af aresampleasync1000 和 -b:a 64k 分别用于控制音频的采样率、时间戳调整和比特率。它们各自有不同的作用&#xff0c;但共同确保音频的质量和同步性。下面我将详细解释每个参数的作用和它们之间的关系。 1. -ar 参数 作用…...

三、FFmpeg学习笔记

​ FFmpeg是一个开源、跨平台的多媒体处理框架&#xff0c;能够实现音视频的录制、转换、剪辑、编码、解码、流媒体传输、过滤与后期处理等几乎所有常见的多媒体操作。其强大之处在于几乎支持所有的音视频格式、编解码器和封装格式&#xff0c;是业界公认的“瑞士军刀”。 FFmp…...

什么是 Java 泛型

一、什么是 Java 泛型&#xff1f; 泛型&#xff08;Generics&#xff09; 是 Java 中一种强大的编程机制&#xff0c;允许在定义类、接口和方法时使用类型参数。通过泛型&#xff0c;可以将数据类型作为参数传递&#xff0c;从而实现代码的通用性和类型安全。 简单来说&…...

从 WPF 到 MAUI:跨平台 UI 开发的进化之路

一、引言 在软件开发领域&#xff0c;用户界面&#xff08;UI&#xff09;开发一直是至关重要的环节。随着技术的不断发展&#xff0c;开发者对于创建跨平台、高性能且美观的 UI 需求日益增长。Windows Presentation Foundation&#xff08;WPF&#xff09;和 .NET Multi - pl…...

Docker学习之dockerfile篇(day8)

文章目录 前言一、问题描述二、具体内容1. Docker 镜像原理2. Docker 镜像制作3. Dockerfile 概念Dockerfile 的基本结构&#xff1a; 4. Dockerfile 关键字5. Docker 实战案例5.1 基于 Nginx 构建 Web 服务器 6. 验证与总结6.1 验证 Dockerfile6.2 总结 前言 Docker 是一种轻…...

Kotlin 作用域函数:apply、let、run、with、also

在 Kotlin 开发中&#xff0c;作用域函数&#xff08;Scope Functions&#xff09;是一组能让代码更简洁、更函数式的高阶函数。它们通过不同的作用域规则和返回值设计&#xff0c;解决了对象配置、空安全处理、链式操作等常见场景问题。本文将结合核心特性、代码示例和对比表格…...

Java 线程池与 Kotlin 协程 高阶学习

以下是Java 线程池与 Kotlin 协程 高阶学习的对比指南&#xff0c;结合具体代码示例&#xff0c;展示两者在异步任务处理中的差异和 Kotlin 的简化优势&#xff1a; 分析&#xff1a; 首先&#xff0c;我们需要回忆Java中线程池的常见用法&#xff0c;比如通过ExecutorService创…...

C++学习笔记(三十三)——forward_list

一、std::forward_list (1) forward_list与其适用场景 std::forward_list 是 C的STL中的单向链表&#xff08;Singly Linked List&#xff09;&#xff0c;它相比 std::list&#xff08;双向链表&#xff09;更轻量&#xff0c;适用于仅需要单向遍历的场景。 主要特点&#…...

ROS订阅相机图像识别颜色并发布识别信息

一、前言 区别于之前的直接驱动相机&#xff0c;这里改为读取图像话题进行处理&#xff0c;原因是如果opencv驱动相机后只能单一使用&#xff0c;就限制了其他识别功能&#xff08;除非将原始图像发布出来&#xff09;&#xff0c;所以这里改成可以读取任意相机图像话题的方法…...

Redis-15.在Java中操作Redis-Spring Data Redis使用方式-操作集合类型的数据

一.操作集合类型的数据 package com.sky.test;import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.*;import j…...

第十一届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

1.字串排序 不会做&#xff0c;感觉挺难的&#xff0c;有兴趣的可以看下面题解 #include <iostream> #include <string.h> using namespace std; int V; int len;//符合交换次数V&#xff0c;字符串长度最小值 int now; //当前已经构造好的那一部分字符串逆序对个数…...

CentOS 安装 zip

安装软件 sudo yum install zip unzip # CentOS 7 sudo dnf install zip unzip # CentOS 8/9压缩文件 # 压缩单个文件 zip 压缩包名.zip 文件1# 压缩多个文件 zip 压缩包名.zip 文件1 文件2 文件3# 压缩目录&#xff08;包含子目录&#xff09; zip -r 压缩包名.zip 目…...

FastPillars:一种易于部署的基于支柱的 3D 探测器

FastPillars&#xff1a;一种易于部署的基于支柱的 3D 探测器Report issue for preceding element Sifan Zhou 1 , Zhi Tian 2 , Xiangxiang Chu 2 , Xinyu Zhang 2 , Bo Zhang 2 , Xiaobo Lu11{}^{1}start_FLOATSUPERSCRIPT 1 end_FLOATSUPERSCRIPT11footnotemark: 1 Chengji…...

LVS高可用负载均衡

一、项目图 二、主机规划 主机系统安装应用网络IPclientredhat 9.5无NAT192.168.72.115/24lvs-masterredhat 9.5ipvsadm&#xff0c;keepalivedNAT192.168.72.116/24 VIP 192.168.72.100/32lvs-backupredhat 9.5ipvsadm&#xff0c;keepalivedNAT192.168.72.117/24 VIP 192.168…...

Kafka延迟队列实现分级重试

技术方案 方案背景 Kafka队列消息消费处理过程中&#xff0c;发生处理异常&#xff0c;需要实现重试机制&#xff0c;并基于重试次数实现不同延迟时间重试方案。 方案介绍 通过实现Kafka延迟队列来实现消息重试机制。 目标&#xff1a; 支持所有业务场景的延迟重试支持多…...

谷粒微服务高级篇学习笔记整理---异步线程池

多线程回顾 多线程实现的4种方式 1. 继承 Thread 类 通过继承 Thread 类并重写 run() 方法实现多线程。 public class MyThread extends Thread {Overridepublic void run() {System.out.println("线程运行: " Thread.currentThread().getName());} }// 使用 pub…...

3.第二阶段x64游戏实战-分析人物移动实现人物加速

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;2.第二阶段x64游戏实战-x64dbg的使用 想找人物的速度&#xff0c;就需要使用Ch…...

MQTT 服务器(emqx)搭建及使用(一)

一. EMQX 服务器搭建 1.下载EMQX 下载链接&#xff1a;Windows | EMQX 文档 官方手册 2.下载内容解压至盘符根目录 3.进入bin文件夹&#xff0c;在地址栏输入cmd 4.依次输入下面命令安装服务 .\emqx.cmd install .\emqx.cmd console 5.设置自启动 创建批处理文件&#x…...

什么是SSE和websocket

以下是 SSE&#xff08;Server-Sent Events&#xff09; 和 WebSocket 在大模型&#xff08;如 ChatGPT&#xff09;流式输出中的实际例子对比&#xff0c;包含代码实现和场景分析&#xff1a; —### 1. SSE&#xff08;Server-Sent Events&#xff09;#### 场景 大模型生成文本…...

蓝桥杯专项复习——二分查找、二分答案

目录 二分查找、二分答案基础知识 二分查找模版 【模版题】数的范围 借教室 二分查找、二分答案基础知识 二分模版 二分查找 【模版题】数的范围 输入样例 6 3 1 2 2 3 3 4 3 4 5输出样例 3 4 5 5 -1 -1 思路&#xff1a; 对应两个模版&#xff0c;起始位置是对应第一…...

Android学习总结之Kotlin 协程

一、引言 在 Android 开发中&#xff0c;异步任务处理是绕不开的话题。传统的线程、Handler、AsyncTask 等方案要么过于繁琐&#xff0c;要么存在生命周期管理问题。Kotlin 协程的出现&#xff0c;以优雅的语法和强大的结构化并发能力&#xff0c;成为解决异步编程难题的理想方…...

docker的与使用

1 docker初体验 1.1 docker简介 问题&#xff1a;为什么会有docker出现&#xff1f; 一款产品从开发到上线&#xff0c;从操作系统&#xff0c;到运行环境&#xff0c;再到应用配置。作为开发运维之间的协作我们需要关心很多东西&#xff0c;这也是很多互联网公司都不得不面对…...

解决ubuntu18.04无法进入系统桌面

解决ubuntu18.04无法进入系统桌面 解决ubuntu18.04无法进入系统桌面前言1、原因2、解决现象总结 前言 Vmware虚拟机运行跑Linux项目&#xff0c;没有关掉运行的进程就关机&#xff0c;导致系统无法进入系统桌面&#xff0c;一直卡在系统的初始化界面&#xff0c;按下快捷键发…...

Docker学习之容器虚拟化与虚拟机的区别(day11)

文章目录 前言一、问题描述二、具体内容1. 虚拟机&#xff08;VM&#xff09;2. 容器虚拟化&#xff08;Docker&#xff09;容器虚拟化的核心技术 三、总结1. 资源占用对比2. 适用场景3. 结论 前言 在现代软件开发和部署过程中&#xff0c;Docker 和虚拟机&#xff08;VM&…...

无人机数据链技术及运行方式详解!

一、无人机数据链技术要点 1. 通信传输技术 频段选择&#xff1a; 常用频段包括 L波段&#xff08;1-2 GHz&#xff09;、C波段&#xff08;4-8 GHz&#xff09;、Ku/K波段&#xff08;12-40 GHz&#xff09;&#xff0c;不同频段在传输距离、带宽和抗干扰性间权衡。 低…...

【JavaEE】MyBatis - Plus

目录 一、快速使用二、CRUD简单使用三、常见注解3.1 TableName3.2 TableFiled3.3 TableId 四、条件构造器4.1 QueryWrapper4.2 UpdateWrapper4.3 LambdaQueryWrapper4.4 LambdaUpdateWrapper 五、自定义SQL 一、快速使用 MyBatis Plus官方文档&#xff1a;MyBatis Plus官方文档…...

设计模式 三、结构型设计模式

一、代理模式 代理设计模式&#xff08;Proxy Design Pattern&#xff09;是一种结构型设计模式&#xff0c;它为其他对象提供了一个代理&#xff0c;以控制对这个对象的访问。 代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。简单来说&#xff0c;代理模式 就是通…...

视频编码器的抉择:x264、x265、libaom、vvenc 对比测试实验

264、x265、libaom、vvenc 对比测试实验 测试机器配置&#xff1a;Apple M1 Pro -16G编码器版本&#xff08;选择自己编译&#xff09;&#xff1a;所有源码都是当前最新更新的状态&#xff0c;此外各类编码具体的编译过程可参考我的相关系列博客。 编码器GitHubx264git clon…...

JMeter脚本录制(火狐)

录制前准备&#xff1a; 电脑&#xff1a; 1、将JMeter证书导入&#xff0c;&#xff08;bin目录下有一个证书&#xff0c;需要安装这个证书到电脑中&#xff09; 2、按winr&#xff0c;输入certmgr.msc&#xff0c;打开证书&#xff0c;点击下一步&#xff0c;输入JMeter证书…...

10、Linux C 网络编程(完整版)

1、网络发展历史和分层 1.1 Internet 的历史 起源&#xff1a; 1957 年&#xff1a;苏联发射第一颗人造卫星 "Sputnik"。 1958 年&#xff1a;美国总统艾森豪威尔成立 DARPA&#xff08;国防部高级研究计划署&#xff09;。 1968 年&#xff1a;DARPA 提出 "…...

拼多多 anti-token unidbg 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 版本7.3-7.4 都试过加密没什…...

Swoole 的 Hyperf 框架和 Go 的 Gin 框架高并发原理以及技术实现对比分析

Swoole 的 Hyperf 框架和 Go 的 Gin 框架虽然都支持高并发&#xff0c;但它们的实现原理、底层机制和适用场景有显著差异。以下从 高并发原理、技术实现区别、优缺点 三个方面详细分析&#xff1a; 一、高并发实现原理 1. Hyperf (PHP Swoole) Hyperf 的高并发能力基于 Swoo…...

CSS3学习教程,从入门到精通,CSS3 媒体查询实现响应式布局语法指南(21)

CSS3 媒体查询实现响应式布局语法指南 一、媒体查询核心语法 1. 基础语法结构 media 媒体类型 and (媒体特性) {/* 匹配条件时应用的CSS规则 */ }2. 媒体类型&#xff08;可省略&#xff09; 类型值说明all所有设备&#xff08;默认值&#xff09;screen屏幕设备print打印机…...

C#中,什么是委托,什么是事件及它们之间的关系

1. 委托&#xff08;Delegate&#xff09; 定义与作用 ‌委托‌是类型安全的函数指针&#xff0c;用于封装方法&#xff0c;支持多播&#xff08;链式调用&#xff09;。‌核心能力‌&#xff1a;将方法作为参数传递或异步回调。 使用场景 回调机制&#xff08;如异步操作完…...

【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)

&#x1f680; 力扣热题 347&#xff1a;前 K 个高频元素&#xff08;详细解析&#xff09; &#x1f4cc; 题目描述 力扣 347. 前 K 个高频元素 给你一个整数数组 nums 和一个整数 k&#xff0c;请你返回其中出现频率 前 k 高的元素。你可以按 任意顺序 返回答案。 &#x1f…...

②EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

型号 协议转换通信网关 EtherCAT 转 Modbus TCP 配置说明 网线连接电脑到模块上的 WEB 网页设置网口&#xff0c;电脑所连网口的网段设置成 192.168.1.X&#xff08;X 是除 8 外的任一数值&#xff09;后&#xff0c;打开浏览器&#xff0c;地址栏输入 192.168.1.8 &#xff…...

微服务集成测试 -华为OD机试真题(A卷、Python)

题目描述 现在有n个容器服务&#xff0c;服务的启动可能有一定的依赖性&#xff08;有些服务启动没有依赖&#xff09;&#xff0c;其次&#xff0c;服务自身启动加载会消耗一些时间。 给你一个n n 的二维矩阵useTime&#xff0c;其中useTime[i][i]10表示服务i自身启动加载需…...

k8s常用总结

1. Kubernetes 架构概览 主节点&#xff08;Master&#xff09;&#xff1a; 负责集群管理&#xff0c;包括 API Server、Controller Manager、Scheduler 和 etcd 存储。 工作节点&#xff08;Node&#xff09;&#xff1a; 运行 Pod 和容器&#xff0c;包含 kubelet、kube-pr…...

【算法】并查集基础讲解

一、定义 一种树型的数据结构&#xff0c;用于处理一些不相交集合的合并及查询问题。思想是用一个数组表示了整片森林&#xff08;parent&#xff09;&#xff0c;树的根节点唯一标识了一个集合&#xff0c;只要找到了某个元素的的树根&#xff0c;就能确定它在哪个集合里。 …...

探索PHP的未来发展与应用趋势

PHP&#xff0c;作为Web开发领域的常青树&#xff0c;自1995年诞生以来&#xff0c;始终在动态网页开发中占据重要席位。随着技术的不断演进&#xff0c;PHP也在持续更新&#xff0c;以适应现代开发需求。本文将深入探讨PHP的最新发展动态及其在2025年的应用趋势。 PHP 8&…...

C#调用ACCESS数据库,解决“Microsoft.ACE.OLEDB.12.0”未注册问题

C#调用ACCESS数据库&#xff0c;解决“Microsoft.ACE.OLEDB.12.0”未注册问题 解决方法&#xff1a; 1.将C#采用的平台从AnyCpu改成X64 2.将官网下载的“Microsoft Access 2010 数据库引擎可再发行程序包AccessDatabaseEngine_X64”文件解压 3.安装解压后的文件 点击下载安…...

ubuntu22.04.5安装docker,解决安装出现的错误,解决Docker hello-world没打印出来

文章目录 前言一 安装失败解决1结合具体报错分析2 首先怀疑是VPN的问题3 直接百度报错信息4最终解决问题 二 验证Docker hello-world没打印出来总结 前言 先说一下前面的情况&#xff0c;使用的是公司的工作站&#xff0c;登录公司一个帐号使用的公司网络&#xff0c;使用网上…...

HMTL+JS+CSS实现贪吃蛇游戏,包含有一般模式,困难模式,还有无敌模式

HMTLJSCSS实现贪吃蛇游戏&#xff0c;包含有一般模式&#xff0c;困难模式&#xff0c;还有无敌模式&#xff08;可以穿墙死不了&#xff0c;从左边进去可以从右边出来&#xff09;&#xff0c;显示当前分数和最高分&#xff0c;吃到的球颜色可以叠加到蛇身体上 为了适配手机端…...

vue将页面导出成word

方法一&#xff1a;使用 html-docx-js html-docx-js 是一个轻量级的库&#xff0c;可以将 HTML 转换为 Word 文档。 安装依赖 首先安装 html-docx-js&#xff1a; Bash深色版本 npm install html-docx-js --save创建导出逻辑 在 Vue 组件中实现导出功能的代码如下&#xff1…...