Spring 常见问题

AI-摘要
gpt-3.5-turbo GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Spring 常见问题
penjc1. Spring Bean的线程安全问题
在Spring框架中,Bean的线程安全性是开发中常见的问题。Spring容器中的默认Bean是**单例(singleton)**作用域,这意味着所有线程都共享同一个实例。因此,单例Bean会面临多线程访问时的线程安全问题。
常见问题和解决方案:
单例模式中的线程安全问题:
- 由于单例模式的Bean会在多个请求中共享同一个实例,因此,当多个线程访问这些Bean时,修改实例中的可变状态可能会导致线程安全问题。
- 例如,
SingletonController
类中的number
变量是共享的。当多个线程并发访问/api/number1
和/api/number2
时,可能会导致并发冲突。
解决方案:
- 使用
@Scope("prototype")
来确保每次请求都会创建新的实例,避免多线程访问时共享同一个实例。或者,将可变的状态使用ThreadLocal
进行存储,每个线程使用自己的独立副本。
原型模式的线程安全:
- 使用
@Scope("prototype")
可以创建多个实例,每个请求都会创建一个新的Bean实例。例如,在PrototypeController
中,number
变量在每次请求中都会被重新初始化,因此没有共享状态,也就没有线程安全问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PrototypeController {
private int number = 0;
public int number1() {
number ++;
System.out.println(this.number);
return this.number;
}
public int number2() {
number ++;
System.out.println(this.number);
return this.number;
}
}- 使用
线程安全控制:使用
ThreadLocal
来确保每个线程都能拥有独立的变量副本。例如,在ThreadSafeController
中使用ThreadLocal
来保证每个线程的numberThreadLocal
变量独立。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadSafeController {
private ThreadLocal<Integer> numberThreadLocal = ThreadLocal.withInitial(() -> 0);
public int number() {
numberThreadLocal.set(1);
System.out.println(numberThreadLocal.get());
return numberThreadLocal.get();
}
public int number2() {
System.out.println(numberThreadLocal.get());
return numberThreadLocal.get();
}
}
2. Spring事务管理
Spring提供了@Transactional
注解来实现事务管理,但是有一些常见的错误和注意事项。
常见事务管理问题和解决方案:
事务回滚:
- 默认情况下,Spring只会在遇到
RuntimeException
及其子类时回滚事务。如果抛出了IOException
等受检查异常(checked exception
),则不会触发回滚。
解决方案:
- 如果需要在
IOException
等异常时回滚事务,可以通过@Transactional
的rollbackFor
属性来指定回滚规则。
1
2
3
4
public int reduceStore(Integer id) throws IOException {
// 代码逻辑
}- 如图所示,如果方法抛出了
IOException
,需要显式声明rollbackFor = IOException.class
才能触发事务回滚。
- 默认情况下,Spring只会在遇到
多线程中的事务失效:
- 事务通常是绑定到当前线程的。在
reduceStore
方法中,如果开启了一个新线程来执行代码(如使用new Thread(() -> {...}).start();
),事务不会随着新线程执行,因为Spring事务管理是基于代理的,不会跨线程传播。
解决方案:
- 避免在事务方法中启动新线程,或者在新线程中手动管理事务。如果需要跨线程事务支持,可能需要使用分布式事务或手动控制事务边界。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int reduceStore(Integer id) {
int update = 0;
Goods goods = goodsDao.queryById(id);
Integer store = goods.getStore();
if (store > 0) {
update = goodsDao.updateStore(id);
}
new Thread(() -> {
// 在此处启动新线程时,事务不会传播到新线程
int b = 10 / 0;
}).start();
return update;
}- 事务通常是绑定到当前线程的。在
传播行为:
- Spring的事务传播行为决定了方法调用时事务的传播方式。例如,
Propagation.NOT_SUPPORTED
表示当前方法执行时不支持事务,这意味着即使方法是在一个事务中被调用的,它也会在没有事务的情况下执行。
1
2
3
4
public int m(Integer id) {
// 代码逻辑
}- 事务传播行为的使用场景:如果方法
- Spring的事务传播行为决定了方法调用时事务的传播方式。例如,
需要执行的操作与事务无关,可以使用NOT_SUPPORTED
。这确保了当前方法不会影响事务的状态,从而可以避免不必要的事务开销。
3. Spring事务失败的常见原因
异常类型错误:
- 默认回滚:Spring默认只会在抛出
RuntimeException
及其子类时触发事务回滚。而checked exception
(受检查的异常)默认不会触发回滚。 - 解决方法:如果希望Spring回滚受检查的异常,可以使用
@Transactional(rollbackFor = Exception.class)
显式指定需要回滚的异常类型。
1
2
3
4
public void processFile() throws IOException {
// 如果发生IOException,将触发事务回滚
}- 默认回滚:Spring默认只会在抛出
方法或类上没有标注
@Transactional
注解:- Spring事务管理只会对标有
@Transactional
的类或方法生效。如果方法没有标注@Transactional
,Spring不会管理该方法的事务。 - 解决方法:确保所有需要事务管理的方法都标注了
@Transactional
注解。
1
2
3
4
public void transferMoney() {
// 方法会受到Spring事务管理
}- Spring事务管理只会对标有
同一类中,方法内部自调用:
- 由于Spring事务是基于代理模式实现的,事务管理是通过代理对象拦截方法调用的。如果方法内部自调用,Spring的代理无法拦截,因此事务不会生效。
- 解决方法:避免在同一个类内自调用事务方法,或者在不同的类中调用方法来触发事务。
1
2
3
4
5
6
7
8
9
public void methodA() {
this.methodB(); // 事务不会生效,因为是自调用
}
public void methodB() {
// 事务逻辑
}事务方法不是
public
的:- Spring事务管理只能拦截
public
方法,因为事务代理是通过CGLIB或JDK动态代理实现的,而代理对象只能拦截public
方法。 - 解决方法:确保所有需要事务管理的方法是
public
的。
1
2
3
4
private void privateMethod() { // 事务不会生效,因为方法是私有的
// 事务逻辑
}- Spring事务管理只能拦截
多线程调用:
- 事务是绑定到当前线程的,如果在事务方法中启动新线程,事务无法传播到新线程,导致事务失效。
- 解决方法:避免在事务方法中创建新线程,或者在新线程中手动管理事务。
1
2
3
4
5
6
public void processOrder() {
new Thread(() -> {
// 新线程中的事务不会生效
}).start();
}异常被
try...catch
捕获:- 如果事务方法中的异常被
try...catch
捕获,Spring事务管理就无法感知到异常,从而无法触发回滚。 - 解决方法:不要在事务方法中捕获不必要的异常,或者捕获后确保异常被重新抛出。
1
2
3
4
5
6
7
8
public void process() {
try {
int result = 10 / 0;
} catch (Exception e) {
e.printStackTrace(); // 捕获异常会导致事务失效
}
}- 如果事务方法中的异常被
手动抛了别的异常:
- Spring事务默认只会回滚
RuntimeException
和Error
,如果手动抛出其他异常(如IOException
),默认不会触发事务回滚。 - 解决方法:确保事务回滚规则适配你抛出的异常,或者使用
@Transactional
的rollbackFor
指定回滚类型。
1
2
3
4
public void processFile() throws IOException {
// 手动抛出IOException时,事务会回滚
}- Spring事务默认只会回滚
事务方法所在的Bean未被Spring容器管理:
- 事务是由Spring容器管理的,如果事务方法所在的Bean没有被Spring管理(例如直接通过
new
创建的对象),则事务管理不会生效。 - 解决方法:确保事务方法所在的类是由Spring容器管理的Bean,不能直接通过
new
实例化。
- 事务是由Spring容器管理的,如果事务方法所在的Bean没有被Spring管理(例如直接通过
方法的事务传播类型不支持事务:
- Spring事务提供了多种传播行为,例如
Propagation.NOT_SUPPORTED
表示当前方法不支持事务。如果方法设置了不支持事务的传播行为,事务管理就会失效。 - 解决方法:检查方法的传播行为,确保需要事务的操作使用合适的传播行为。
1
2
3
4
public void doNotSupportTransaction() {
// 当前方法不参与事务
}- Spring事务提供了多种传播行为,例如
表的数据库引擎不支持事务:
- 某些数据库引擎(例如MySQL的
MyISAM
引擎)不支持事务,因此即使Spring事务管理正常配置,事务也不会生效。 - 解决方法:确保使用支持事务的数据库引擎(例如MySQL的
InnoDB
引擎)。
1 | -- 确保使用支持事务的数据库引擎,如 InnoDB |
4. Spring事务回滚机制与异常
Spring事务的回滚机制默认只会在遇到RuntimeException
时回滚。如果想让某些受检查的异常(如IOException
)触发回滚,需要显式指定rollbackFor
属性。
Spring默认回滚的异常:
- 默认情况下,
@Transactional
只会回滚RuntimeException
及其子类。
显式指定回滚的异常:
- 如果方法中会抛出受检查异常,如
IOException
,可以通过@Transactional(rollbackFor = IOException.class)
指定回滚。
1 |
|
评论
匿名评论
✅ 你无需删除空行,直接评论以获取最佳展示效果