闲谈
大家好,我是了不起
,最近了不起在当面试官,可以说是最直接的接触行情了,每天都会遇到很多情况。
前两天线上面试一个人,最后发现他使用gpt在面试,一开始不正面回答问题,一直在跟我扯“你说的是XXX的那个吗?我听的不太清楚”一类的,支支吾吾拖延时间,十来秒之后又什么问题都能答上来。
随后了不起
特地问了个跨行业的技术问题(电力通讯),他不知道超纲了,但是也在支支吾吾十来秒后回答上了。
算了,不提也罢,回归正题,分享一道我最近常用来面试1-2年工作经验的面试题吧。
什么是Spring事务?
Spring事务管理是企业级应用开发中的关键技术之一,确保数据一致性和完整性。
Spring提供了灵活且强大的事务管理机制,通过声明式和编程式的方式让开发者轻松管理事务,同时解耦业务逻辑与事务控制。
Spring的事务管理涵盖了事务的开始、提交、回滚等过程,并为不同的持久化技术(如JDBC、JPA、Hibernate等)提供了统一的事务管理。
在Spring中,声明式事务管理通常使用@Transactional
注解来实现。这个注解可以应用到类级别或方法级别,用于指定事务的行为。
比如:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public UserService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public void createUser(String name, String email) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
// 模拟一个异常,测试事务回滚
if ("error".equals(name)) {
throw new RuntimeException("模拟错误");
}
}
}
在这个例子中,createUser
方法被标记为@Transactional
,这意味着如果在这个方法执行过程中发生任何未捕获的异常,事务将会回滚,从而保证数据的一致性。
一般上面的内容,都用于简单解释什么是Spring事务。
但是面试的时候就没那么简单,面试官往往会问你,Spring事务在什么情况下会失效?
在搞清楚为什么会失效之前,我们需要先明白Spring事务的原理!Spring的@Transactional是基于Spring的AOP机制去实现的,AOP又是基于动态代理实现的,那么第一个答案就出来了,代理失效了,Spring事务就失效了
这个回答属于粗浅的回答,基本上只能满足于应届水平的面试,因为背答案就行了。
但是如果你面的是一至三年工作经验的程序员,面试官可不会就这么放过你。
他会接着问,什么情况下,代理会失效?
这方面,可以由浅到深去回答,
比如最简单的就是:
事务注解没有被启用,也就是没有在配置类上添加上
@EnableTransactionManagement
注解,以及 数据源未配置事务管理器,数据库引擎不支持事务(InnoDB)同一个类中,没有事务的A方法,调用了带事务的B方法,而你直接使用的是A方法,即:当在一个事务方法内部调用同一个类中的另一个事务方法时,外部方法的事务不会传播到内部方法,除非使用了特定的传播行为(如
REQUIRES_NEW
)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public UserService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public void createUser(String name, String email) {
internalCreateUser(name, email);
}
@Transactional
public void internalCreateUser(String name, String email) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
if ("error".equals(name)) {
throw new RuntimeException("模拟错误");
}
}
}
在这个例子中,createUser
方法调用了internalCreateUser
方法。由于它们都在同一个类中,所以internalCreateUser
方法的事务配置不会生效。如果internalCreateUser
方法抛出异常,事务不会回滚。
解决方案:将internalCreateUser
方法移到另一个服务类中,或者使用@Transactional(propagation = Propagation.REQUIRES_NEW)
来确保每个方法都有独立的事务。
异常被捕获但是没有抛出
在这个例子中,createUser
方法中的异常被捕获并处理了,但没有重新抛出。因此,事务管理器不会回滚事务。
@Transactional
public void createUser(String name, String email) {
try {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
if ("error".equals(name)) {
throw new RuntimeException("模拟错误");
}
} catch (Exception e) {
// 异常被捕获但没有重新抛出
System.err.println("捕获到异常: " + e.getMessage());
}
}
解决方案:让异常继续向上抛出,或者显式地调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
来手动设置事务回滚。
rollbackFor属性配置错误
默认情况下,Spring事务只在遇到RuntimeException时回滚,对于其他异常可能无法生效。
@Transactional
public void createUser(String name, String email) throws Exception {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
if ("error".equals(name)) {
throw new Exception("模拟错误");
}
}
可以通过在@Transactional注解中添加rollbackFor属性,明确指定需要回滚的异常类型。
@Transactional(rollbackFor = Exception.class)
public void createUser(String name, String email) throws Exception {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
if ("error".equals(name)) {
throw new Exception("模拟错误");
}
}
事务方法不是公共方法
如果事务方法不是用public
来修饰,那么事务管理器就无法代理这个方法,事务也就失效了。
举例:
@Transactional
protected void createUser(String name, String email) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
if ("error".equals(name)) {
throw new RuntimeException("模拟错误");
}
}
解决方案:将方法声明为public
。
@Transactional
public void createUser(String name, String email) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
if ("error".equals(name)) {
throw new RuntimeException("模拟错误");
}
}