mysql
帆软 Java ⾯试
看到⼀张⽹传的不加班、不内卷、不减薪,甚⾄ 955 的神仙公司:
其中⾥⾯有帆软这家公司,帆软是⼀家专注 To B 领域的软件公司,⼯作时间也没有那么卷,955
并⽆强制加 班,⽽且研发岗位主要在⼆线城市,⽐如成都、南京等。

前段时间,也有同学跟我反馈,他已经通关了** 帆软所有⾯试,已经拿到录取意向,** 就等谈薪资
了帆软的薪资开的还是很不错的,校招毕业能拿到 20k+ 的⽉薪,算上年终,也有 30w 的年薪了,
跟⼀线互联⽹⼤⼚其实差距不算很⼤,⽽且sp offer 薪资估计会更⾼,会多 2-4k 。
那话说回来,帆软的校招⾯试难度如何呢?
这次就来跟分享⼀位** 校招同学的帆软Java 后端开发的⾯经,** 主要考察了Java 、MySQL 、分布式锁这三⼤块内容。

分布式
分布式锁除了redisson ,还能怎么实现?
** 基于MySQL :** 通过 for update 排他锁,对特定数据记录加排他锁。我们可以当做查询到数
据时,则获取到分 布式锁,接下来执⾏⽅法中的逻辑。在⽅法执⾏完毕后,提交事 务,锁会⾃
动释放。
基于 ZooKeeper :利⽤临时顺序节点来实现分布式锁的获取和释放。
zookerper 实现分布式锁和redisson 实现分布式的区别?
zookerper 实现的分布式锁是强⼀致性的,** 因为它底层的 ZAB 协议(原⼦⼴播协议), 天然满⾜CP ,** 但是这也意味着性能的下降, 所以不站在具体数据下看 Redis 和 Zookeeper, 代表着性能和⼀
致性的取舍
如果项⽬没有强依赖 ZK, 使⽤ Redis 就好了, 因为现在 Redis ⽤途很⼴, ⼤部分项⽬中都引⽤了
Redis ,没必要对此再引⼊⼀个新的组件, 如果业务场景对于 Redis 异步⽅式的同步数据造成锁丢失
⽆法忍受, 在业务层处理就好了
cap 是啥?
CAP 原则⼜称 CAP 定理, 指的是在⼀个分布式系统中, Consistency (⼀致性)、 Availability (可⽤
性)、Partition tolerance (分区容错性), 三者不可得兼

⼀致性(C) : 在分布式系统中的所有数据备份, 在同⼀时刻是否同 样的值(等同于所有节点访问同
⼀份最新的数据副本)
可⽤性(A): 在集群中⼀部分节点故障后, 集群整体是否还能响应客⼾端的读写请求(对数据更新具
备⾼可⽤性)
分区容忍性(P): 以实际效果⽽⾔, 分区相当于对通信的时限要求. 系统如果不能在时限内达成数据⼀致性, 就意味着发⽣了分区的情况, 必须就当前操作在 C 和 A 之间做出选择
redisson 对于cap 定理的体现是什么 ?
主要是 ap 模型,牺牲 ⼀致性,换取可⽤性。
Redis Cluster 通过数据分⽚(Sharding )和主从 复制(Master-Slave Replication )实现了⾼可⽤性和⼀定的数据冗余。在Redis Cluster 中,每个节点都保存了部分数据,并通过复制机制保证数据在多个节点上的可⽤性。
分区容忍性(P):Redis Cluster 天⽣⽀持分区容忍性,通过多个节点和分⽚机制,即使部分节
点或⽹络发⽣故障,系统仍能继续运⾏。
⼀致性(C)与可⽤性(A)的权衡:Redis Cluster 在默认情况下更倾向于可⽤性(A)。在数据
复制过程中,Redis 采⽤了异步复制机制,这意味着主节点写⼊数据后,不会等待所有从节点确
认就返回操作成功的结果。这样做提⾼了系统的响应速度和可 ⽤性,但在极端情况下(如主节
点宕机且未完成数据同步)可能会导致数据丢失,即牺牲 了强⼀致性(C)。
redis 实现的分布式锁还有什么 缺点?红锁是什么 ?
基于 Redis 实现分布式锁的缺点:
超时时 间不好设置。如果锁的超时时 间设置过⻓,会影响性能,如果设置的超时时 间过短会保
护不到共享资源。⽐如在有些场景中,⼀个线程 A 获取到了锁之后,由于业 务代码执⾏时间可
能⽐较⻓,导致超过了锁的超时时 间,⾃动失效,注意 A 线程没执⾏完,后续线程 B ⼜意外的
持有了锁,意味着可以操作共享资源,那么两个 线程之间的共享资源就没办法进⾏保护了。
那么如何合理设置超时时 间呢? 我们可以基于续约的⽅式设置超时时 间:先给锁设置⼀个超时时间,然后启 动⼀个守护线程,让守护线程在⼀段时间后,重新设置这个锁的超时时 间。实现
⽅式就是:写⼀个守护线程,然后去判断锁的情况,当锁快失效的时候,再次进⾏续约加锁,
当主线程执⾏完成后,销毁续约锁即可,不过这 种⽅式实现起来相对复杂。
Redis 主从 复制模式中的数据是异步复制的,这样导致分布式锁的不可靠性。如果在 Redis 主节
点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可
以获取锁,所以多个应⽤服务就可以同时获取到锁。
Redis 如何解决集群情况下分布式锁的可靠性?
为了 保证集群环境下分布式锁的可靠性,Redis 官⽅已经设计 了⼀个分布式锁算法 Redlock (红
锁)。
它是基于多个 Redis 节点的分布式锁,即使有节点发⽣了故障,锁变量仍然是存在的,客⼾端还
是可以完成锁操作。官⽅推荐是⾄少部署 5 个 Redis 节点,⽽且都是主节点,它们之间没有任何
关系,都是⼀个个 孤⽴的节点。
Redlock 算法的基本思路,是让客⼾端和多个独⽴的 Redis 节点依次请求申请加锁,如果客⼾端能
够和半数以上的节点成功地完成加锁操作,那么我们就认为,客⼾端成功地获得分布式锁,否则加锁失败。
这样⼀来,即使有某 个 Redis 节点发⽣故障,因为锁的数据在其他节点上也 有保存,所以客⼾端仍
然可以正常地进⾏锁操作,锁的数据也不 会丢失。
Redlock 算法加锁三个 过程:
第⼀步是,客⼾端获取当前时间(t1 )。
第⼆步是,客⼾端按顺序依次向 N 个 Redis 节点执⾏加锁操作:
加锁操作使 ⽤ SET 命令,带上 NX ,EX/PX 选项,以及带上客⼾端的唯⼀标识。
如果某 个 Redis 节点发⽣故障了,为了 保证在这种情况下,Redlock 算法能够继续运⾏,我们需
要给「加锁操作」设置⼀个超时时 间(不是对「锁」设置超时时 间,⽽是对「加锁操作」设置
超时时 间),加锁操作的超时时 间需要远远 地⼩于锁的过期时间,⼀般也就是设置为⼏⼗毫秒。
第三步是,⼀旦客⼾端从超过半数(⼤于等于 N/2+1 )的 Redis 节点上成功获取到了锁,就再
次获取当前时间(t2 ),然后计算计算整个加锁过程的总耗时(t2-t1 )。如果 t2-t1 < 锁的过期时间,此时,认为客⼾端加锁成功,否则认为加锁失败。
可以看到,加锁成功要同时满⾜两个 条件(
简述:如果有 超过半数的 Redis
节点成功的获取到了
锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功):
条件⼀:客⼾端从超过半数(⼤于等于 N/2+1 )的 Redis 节点上成功获取到了锁;
条件⼆:客⼾端从⼤多数节点获取锁的总耗时(t2-t1 )⼩于锁设置的过期时间。
加锁成功后,客⼾端需要重新计算这把锁的有效时间,计算的结果是「锁最初设置的过期时间」
减去「客⼾端从⼤多数节点获取锁的总耗时(t2-t1 )」。如果计算的结果已经来不及完成共享数据
的操作了,我们可以释放锁,以免出现还没完成数据操作,锁就过期了的情况。
加锁失败后,客⼾端向所有 Redis 节点发起释放锁的操作,释放锁的操作和在单节点上释放锁的
操作⼀样,只要执⾏释放锁的 Lua 脚本就可以了。
mysql
mysql 事务实现原理说明⼀下
mysql 事务的 acid 特性如下:
原⼦性(Atomicity ):⼀个事 务中的所有操作,要么全部完成,要么全部不完成,不会结束在
中间某个环节,⽽且事 务在执⾏过程中发⽣错误,会被回滚到事务开始前的状态,就像这个事
务从来没有执⾏过⼀样,就好⽐买⼀件商品,购买成功时,则给商家付了 钱,商品到⼿;购买
失败时,则商品在商家⼿中,消费者的钱也没花出去。
⼀致性(Consistency ):是指事务操作前和操作后,数据满⾜完整性约束,数据库保持⼀致性
状态。⽐如,⽤⼾ A 和⽤⼾ B 在银⾏分别 有 800 元和 600 元,总共 1400 元,⽤⼾ A 给⽤⼾ B
转账 200 元,分为两个 步骤,从 A 的账⼾扣除 200 元和对 B 的账⼾增加 200 元。⼀致性就是
要求上述步骤操作后,最后的结果是⽤⼾ A 还有 600 元,⽤⼾ B 有 800 元,总共 1400 元,⽽
不会出现⽤⼾ A 扣除了 200 元,但⽤⼾ B 未增加的情况(该情况,⽤⼾ A 和 B 均为 600 元,
总共 1200 元)。
隔离性(Isolation ):数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可
以防⽌多个事 务并发执⾏时由于交 叉执⾏⽽导致数据的不⼀致,因为多个事 务同时使⽤相同的
数据时,不会相互⼲扰,每个事 务都有⼀个完整的数据空间,对其他并发事务是隔离的。也就
是说,消费者购买商品这个事 务,是不影响其他消费者购买的。
持久性(Durability ):事务处理结束后,对数据的修改就是永久的,即便系统故障也不 会丢
失。
MySQL InnoDB 引擎通过什么 技术来 保证事务的这四个特性的呢?
持久性是通过 redo log (重做⽇志)来保证的;
原⼦性是通过 undo log (回滚⽇志) 来保证的;
隔离性是通过 MVCC (多版本并发控制) 或锁机制来保证的;
⼀致性则是通过持久性+原⼦性+隔离性来保证;
binlog redolog undolog 都是⼲啥的?
redo log 重做⽇志,是 Innodb 存储引擎层⽣成的⽇志,实现了事 务中的持久性,主要⽤于掉电
等故障恢复;
undo log 回滚⽇志,是 Innodb 存储引擎层⽣成的⽇志,实现了事 务中的原⼦性,主要⽤于事
务回滚和 MVCC 。
bin log ⼆进制⽇志,是 Server 层⽣成的⽇志,主要⽤于数据备份和主从 复制;
explain 你看哪些字段?
explain 是查看 sql 的执⾏计划,主要⽤来分析 sql 语句的执⾏过程,⽐如有没有⾛索引,有没有
外部排序,有没有索引覆盖等等 。
如下图,就是⼀个没有使⽤索引,并且是⼀个全表扫描的查询语句。

对于执⾏计划,参数有:
possible_keys 字段表⽰可能⽤到的索引;
key 字段表⽰实际⽤的索引,如果这⼀项为 ,说明没有使⽤索引;
key_len 表⽰索引的⻓度;rows 表⽰扫描的数据⾏数。
type 表⽰数据扫描类型,我们需要重点看这个。
type 字段就是描述了找到所需数据时使⽤的扫描⽅式是什么 ,常⻅扫描类型的执⾏效率从低到⾼
的顺序为:
All (全表扫描):在这些情况⾥,all 是最坏的情况,因为采⽤了全表扫描的⽅式。
index (全索引扫描):index 和 all 差不多,只不过 index 对索引表进⾏全扫描,这样做的好处
是不再需要对数据进⾏排序,但是开销依然很⼤。所以,要尽量避免全 表扫描和全索引扫描。
range (索引范围扫描):range 表⽰采⽤了索引范围扫描,⼀般在 where ⼦句中使⽤ < 、>、in 、between 等关键词,只检索给定范围的⾏,属于范围查找。从这⼀级别开始,索引的作⽤
会越来越明显 ,因此我们需要尽量让 SQL 查询可以使 ⽤到 range 这⼀级别及以上的 type 访问
⽅式。
ref (⾮唯⼀索引扫描):ref 类型表⽰采⽤了⾮唯⼀索引,或者是唯⼀索引的⾮唯⼀性前缀,返
回数据返回可能是多条。因为虽然使⽤了索引,但该索引列的值并不唯⼀,有重复。这样即使使⽤索引快 速查找到了第⼀条数据,仍然不能停⽌,要进⾏⽬标值附近的⼩范围扫描。但它的
好处 是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在⼀个⾮常⼩的范围内扫
描。
eq_ref (唯⼀索引扫描):eq_ref 类型是使⽤主键或唯⼀索引时产⽣的访问⽅式,通常使⽤在多
表联查中。⽐如,对两张表进⾏联查,关联条件是两张表的 user_id 相等,且 user_id 是唯⼀索
引,那么使⽤ EXPLAIN 进⾏执⾏计划查看的时候,type 就会显⽰ eq_ref 。const (结果只有⼀条的主键或唯⼀索引扫描):const 类型表⽰使⽤了主 键或者唯⼀索引与常量
值进⾏⽐较,⽐如 select name from product where id=1 。需要说明的是 const 类型和 eq_ref都使⽤了主 键或唯⼀索引,不过这 两个 类型有所区别,const 是与常量进⾏⽐较,查询效率会更
快,⽽ eq_ref 通常⽤于多表联查中。
extra 显⽰的结果,这⾥说⼏个重要的参考指标:
Using filesort :当查询语句中包含 group by 操作,⽽且⽆法利⽤索引完成排序操作的时候,
这时不得不选择相应的排序算法进⾏,甚⾄可能会通过⽂件排序,效率是很低的,所以要避免
这种问题的出现。
Using temporary :使了⽤临时表保存中间结果,MySQL 在对查询结果排序时使⽤临时表,常
⻅于排序 order by 和分组查询 group by 。效率低,要避免这种问题的出现。
Using index :所需数据只需在索引即可全部获得,不须要再到表中取数据,也就是使⽤了覆盖
索引,避免了回表操作,效率不错。
mysql update 会上哪些锁?
如果是可重复读隔离级别下,会加⾏级锁,主要有:
记录锁:锁住的是⼀条记录。⽽且记录锁是有 S 锁和 X 锁之分的,满⾜读写互斥,写写 互斥
间隙 锁:只存在于可重复读隔离级别,⽬的是为了 解决可重复读隔离级别下幻读的现象。
Next-Key Lock 称为临 键锁 ,是 Record Lock + Gap Lock 的组合,锁定⼀个范围,并且锁定记
录本⾝。
当我范围查询的时候,他是怎么上 锁的?
唯⼀索引的范围查询加锁规则
当唯⼀索引进⾏范围查询时,会对每⼀个扫描到的索引加 next-key 锁,然后如果遇到下⾯这些情
况,会退化成记录锁或者间隙 锁:
情况⼀:针对「⼤于等于」的范围查询,因为存在等值查询的条件,那么如果等值查询的记录
是存在于表中,那么该记 录的索引中的 next-key 锁会退化成记录锁。
情况⼆:针对「⼩于或者⼩于等于」的范围查询,要看条件值的记录是否存在于表中:
当条件值的记录不在表中,那么不 管是「⼩于」还是「⼩于等于」条件的范围查询,扫描到终
⽌范围查询的记录时,该记 录的索引的 next-key 锁会退化成间隙 锁,其他扫描到的记录,都是
在这些记录的索引上加 next-key 锁。
当条件值的记录在表中,如果是「⼩于」条件的范围查询,扫描到终⽌范围查询的记录时,该记录的索引的 next-key 锁会退化成间隙 锁,其他扫描到的记录,都是在这些记录的索引上加
next-key 锁;如果「⼩于等于」条件的范围查询,扫描到终⽌范围查询的记录时,该记 录的索
引 next-key 锁不会退化成间隙 锁。其他扫描到的记录,都是在这些记录的索引上加 next-key
锁。
⾮唯⼀索引的范围查询加锁规则
⾮唯⼀索引和主键索引的范围查询的加锁也有所不同,不同之处在于⾮唯⼀索引范围查询,索引
的 next-key lock 不会有退化为间隙 锁和记录锁的情况,也就是⾮唯⼀索引进⾏范围查询时,对⼆
级索引记录加锁都是加 next-key 锁。
详⻅:保姆级教程!2 万字 + 30 张图搞懂 MySQL 是怎么加⾏级锁的?
查询的时候,字段必须建⽴索引吗?
什么 时候适⽤索引?
字段有唯⼀性限制的,⽐如商品编码;
经常⽤于 WHERE 查询条件的字段,这样能够提⾼整个表的查询速度,如果查 询条件不是⼀个
字段,可以建⽴联合索引。
经常⽤于 GROUP BY 和 ORDER BY 的字段,这样在查询的时候就不需要再去做⼀次排序了,因
为我们都已经知道了建⽴索引之后在 B+Tree 中的记录都是排序好的。
什么 时候不需要创建索引?
WHERE 条件, GROUP BY , ORDER BY ⾥⽤不到的字段,索引的价值是快速定位,如果起不到
定位的字段通常是不需要创建索引的,因为索引是会占⽤物理空间的。
字段中存在⼤量重 复数据,不需要创建索引,⽐如性别字段,只有男⼥,如果数据库表中,男
⼥的记录分布均匀,那么⽆论搜索哪个值都可能得到⼀半的数据。在这些情况下,还不如不要
索引,因为 MySQL 还有⼀个查询优化器, 查询优化器发现某个值出现在表的数据⾏中的百 分⽐
很⾼的时候,它⼀般会忽略索引,进⾏全表扫描。
表数据太少的时候,不需要创建索引;
经常更新的字段不⽤创建索引,⽐如不要对电商项⽬的⽤⼾余额建⽴索引,因为索引字段频繁
修改,需要额外的操作维护索引的有序性
Java
线程池提交任务的流程是什么 ?
线程池是为了 减少频繁的创建线程和销毁线程带来的性能损耗,线程池的⼯作原理如下图:

线程池分为核⼼线程池,线程池的最⼤容量,还有等待任务的队列,提交⼀个任务,如果核⼼线
程没有满,就创建⼀个线程,如果满了,就是会加⼊等待队列,如果等待队列满了,就会增加线
程,如果达到最⼤线程数量,如果都达到最⼤线程数量,就会按照⼀些丢 弃的策略进⾏处理。
知道什么 是AQS 嘛?底层实现原理是什么 ?
AQS 是抽象的队列同步器, 是⽤来构 建锁或其他同步组件的重量 级基础框架及整个 JUC 体系的基
⽯。
下边的组件都是基于 AQS 框架扩展实现的:
ReentrantLock :可重⼊锁,避免多线程竞 争资源的安全问题
Semaphore :信号量,限制多线程的访问数量
CountDownLatch :计数器, ⽤于线程之间的等待场景(如线程A等待其他多个线程完成任务
后,线程A才能执⾏⾃⼰的任务)
CyclicBarrier :回环栅栏 ,⽤于线程之间的等待场景(如在⼀组线 程中,如果线程A执⾏到代码
段S点就会停下等待,等到组内其 他线程都执⾏到S点时它们才会⽴刻⼀起执⾏剩余的任务)
虽然这些组件在多线程场景下有不同的作⽤,但代 码中也 有相似之处,如都需要管理锁状态,维
护阻塞线程,维护唤醒线程。⽽ AQS 的作⽤就是将这些相似的、公共 的代码封装在⼀起。
AQS 核⼼思想是,如果被请求的共享资源空闲,那么就将 当前请求资源的线程设置为有效的⼯作线
程,将共享资源设置为锁定状态;如果共享资源被占⽤,就需要⼀定的阻塞等待唤醒机制来保证
锁分配。这个机制主要⽤的是CLH 队列的变体实现的,将暂时 获取不到锁的线程加⼊到队列中。
AQS 中的队列是CLH (单向链表)变体的虚拟双向队列(FIFO ),AQS 是通过将每条请求共享资源
的线程封装成⼀个节点来实现锁的分配。
主要原理图如下:

AQS 使⽤⼀个Volatile 的int 类型的成员变量来表⽰同步状态,通过内置的FIFO 队列来完成资源获取
的排队⼯作,通过CAS 完成对State 值的修改。
AQS ⼴泛⽤于控制并发流程的类,如下图:

其中 Sync 是这些类中都有的内部类,其结构如下:

可以看到: Sync 是 AQS 的实现。 AQS 主要完成的任务:
同步状态(⽐如说计数器) 的原⼦性管理;
线程的阻塞和解除阻 塞;
队列的管理。
AQS 原理
AQS 最核⼼的就是三⼤部分:
状态:state ;
控制线程抢锁和配合的FIFO 队列(双向链表);
期望 协作⼯具类去实现的获取/释放等重要⽅法(重写)。
状态state
这⾥state 的具体含义,会根据具体实现类的不同⽽不同:⽐如在Semapore ⾥,他表⽰剩余许可
证的数量;在CountDownLatch ⾥,它表⽰还需要倒数的数量;在ReentrantLock 中,state ⽤来
表⽰“锁”的占有情况,包括可重⼊计数,当state 的值为0的时候,标识该 Lock 不被任何 线程所占
有。
state 是volatile 修饰的,并被并发修改,所以修改state 的⽅法都需要保证线程安全,⽐如
getState 、setState 以及compareAndSetState 操作来读取和更新这个状态。这些⽅法都依赖于
unsafe 类。
FIFO 队列
这个队列⽤来存放“等待的线程,AQS 就是“排队管理器”,当多个线程争⽤同⼀把锁时,必须有
排队机制将那些没能拿到锁的线程串在⼀起。当锁释放时,锁管理器就会挑选⼀个合适的线程
来占有这个刚刚 释放的锁。
AQS 会维护⼀个等待的线程队列,把线程都放到这个队列⾥,这个队列是双向链表形式 。
实现获取/释放等⽅法
这⾥的获取和释放⽅法,是利⽤AQS 的协作⼯具类⾥最重要的⽅法,是由协作类⾃⼰去实现
的,并且含义各不相同;
获取⽅法:获取操作会以 来state 变量,经常会阻塞(⽐如获取不到锁的时候)。在Semaphore
中,获取就是acquire ⽅法,作⽤是获取⼀个许可证; ⽽在CountDownLatch ⾥⾯,获取就是
await ⽅法,作⽤是等待,直到倒数结束;
释放⽅法:在Semaphore 中,释放就是release ⽅法,作⽤是释放⼀个许可证; 在
CountDownLatch ⾥⾯,获取就是countDown ⽅法,作⽤是将倒数的数减⼀;
需要每个实现类重写tryAcquire 和tryRelease 等⽅法。
CAS 操作说⼀下
CAS 叫做CompareAndSwap ,⽐较并交换,主要是通过处理器的指令来保证操作的原⼦性,它包
含三个 操作数:
变量内存地址 ,V表⽰
旧的预期值,A表⽰
准备设置的新值,B表⽰
当执⾏CAS 指令时,只有当V等于A时,才会⽤B去更新V的值,否则就不会执⾏更新操作。
CAS 的缺点主要有3点:
ABA 问题:ABA 的问题指的是在CAS 更新的过程中,当读取到的值是A,然后准备赋值的时候仍
然是A,但是实际上有可能A的值被改成了B,然后⼜被改回了A,这个CAS 更新的漏洞就叫做
ABA 。只是ABA 的问题⼤部分场景下都不影响并发的最终效果。Java 中有
AtomicStampedReference 来解决这个问题,他加⼊了预期标志和更新后标志两个 字段,更新时
不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。
循环时间⻓开销⼤:⾃旋CAS 的⽅式如果⻓时间不成功,会给CPU 带来很⼤的开销。
只能保证⼀个共享变量的原⼦操作:只对⼀个共享变量操作可以保 证原⼦性,但是多个则不
⾏,多个可以通过AtomicReference 来处理或者使⽤锁synchronized 实现。
Spring 事务如果没有回滚可能是什么 原因?
在 Spring Boot 中,造成事务不⾃动回滚的场景有很多,⽐如以下这些:
⾮ public 修饰的⽅法中的事务不⾃动回滚,解决⽅案是将⽅法的权限修饰符改为 public 即可。
当 @Transactional 遇上 try/catch 事务不⾃动回滚,解决⽅案有两种:⼀种是在 catch 中将异常
重新抛出去,另⼀种是使⽤代码⼿动将事务回滚。
调⽤类内部的 @Transactional ⽅法事务不⾃动回滚,解决⽅案是给调⽤的⽅法上也 加上
@Transactional抛出检查异常时事务不⾃动回滚,解决⽅案是给 @Transactional 注解上,添加 rollbackFor 参数
并设置 Exception.class 值即可数据库不⽀持事务,事务也不 会⾃动回滚,⽐如 MySQL 中设置了使⽤ MyISAM 引擎,因为它
本⾝是不⽀持事务的,这种情况下,即使在程序中添加了 @Transactional 注解,那么依然不会
有事务的⾏为,也就不会执⾏事务的⾃动回滚了。解决⽅式就是将 MySQL 的存储引擎设置为
InnoDB ,InnoDB ⽀持事务。
