一、顶层分类(Java 异常体系)
java
Throwable
├─ Error 错误(系统级,不用捕获)
└─ Exception 异常
├─ RuntimeException 运行时异常(非受检异常)
└─ 其他Exception(受检异常,必须try-catch/throws)
└─ IOException 属于这类
1. Error
- 归属:
Throwable直接子类,不是 Exception - 性质:系统严重错误,程序无法恢复
- 典型例子:
- OOM:
OutOfMemoryError - 栈溢出:
StackOverflowError - 类加载失败:
NoClassDefFoundError
- OOM:
- 特点:
- 编译器不强制捕获 / 抛出
- 发生后程序基本只能崩溃,无法处理
- Spring
@Transactional默认遇到 Error 会回滚事务
2. RuntimeException 运行时异常(非受检异常 Unchecked)
- 归属:
Exception的子类 - 性质:代码逻辑错误,运行时才暴露
- 典型:
NullPointerException空指针ArrayIndexOutOfBoundsException数组越界IllegalArgumentException参数非法ArithmeticException除零
- 特点:
- 编译器不强制 try-catch /throws
- 属于代码 bug,规范编码可避免
@Transactional默认遇到它自动回滚
3. IOException IO 异常(受检异常 Checked Exception)
- 归属:直接继承
Exception,不是 RuntimeException - 性质:外部资源故障,和业务无关,无法完全避免
- 典型:文件读写、网络连接、流关闭失败
- 特点:
- 编译器强制处理:要么 try-catch,要么方法 throws IOException
- Spring 默认不会触发事务回滚 这是最关键业务区别!
二、核心对比表
表格
| 类型 | 受检 / 非受检 | 默认 @Transactional 是否回滚 | 产生原因 | 是否强制捕获 |
|---|---|---|---|---|
| Error | 非受检 | ✅ 自动回滚 | JVM / 系统底层故障 | 不强制 |
| RuntimeException | 非受检 | ✅ 自动回滚 | 代码逻辑 bug | 不强制 |
| IOException | 受检 Checked | ❌ 默认不回滚 | IO、网络、文件异常 | 必须捕获 / 抛出 |
三、@Transactional 事务回滚规则重点
Spring 默认回滚规则:
只有 RuntimeException、Error 才会自动回滚 所有普通 Checked 异常(IOException、SQLException 等)捕获 / 抛出都不回滚
场景示例
java
// 默认配置,IO异常不会回滚
@Transactional
public void readFile() throws IOException {
insertDb();
readFile(); // 抛IOException,数据库插入不会回滚
}
解决:指定所有异常都回滚
java
@Transactional(rollbackFor = Exception.class)
public void readFile() throws IOException {
insertDb();
readFile(); // IOException也会触发回滚
}
四、简单记忆口诀
- Error:系统崩了,管不了,事务回滚
- RuntimeException:代码写错,运行炸,事务回滚
- IOException:外部 IO 问题,编译器逼你处理,默认不回滚事务
五、Java 处理 IOException 的两种标准方式
IOException 是受检异常(Checked Exception),编译器强制要求必须处理,二选一:
方式 1:throws 向上抛出(推荐 Service / 工具层)
方法声明抛出异常,交给上层调用方处理,代码干净,适合事务场景。
java
import java.io.FileReader;
import java.io.IOException;
// 抛出IOException,由调用者捕获
public static String readFile() throws IOException {
FileReader reader = new FileReader("test.txt");
char c = (char) reader.read();
reader.close();
return String.valueOf(c);
}
配合 @Transactional 注意
只抛 IOException 默认不会回滚事务,需要配置:
java
@Transactional(rollbackFor = Exception.class)
public void biz() throws IOException {
readFile();
}
方式 2:try-catch 本地捕获(当前方法内处理)
适合在当前方法就能兜底、不需要上层感知的场景。
java
public static void readFileCatch() {
FileReader reader = null;
try {
reader = new FileReader("test.txt");
System.out.println((char) reader.read());
} catch (IOException e) {
// 异常处理:打印日志、返回错误码、抛出自定义业务异常
e.printStackTrace();
log.error("读取文件失败", e);
throw new BusinessException("文件读取异常");
} finally {
// 必须关闭流,释放资源
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
log.error("关闭流失败", e);
}
}
}
六、优雅写法:try-with-resources(JDK7+,最优)
实现 AutoCloseable 的流(FileReader、InputStream、Socket 等)自动关闭,不用手动写 finally 关闭,大幅简化 IO 代码。
java
// 资源写在try()中,代码块结束自动关闭,无论正常/异常
public static void readFileAutoClose() {
try (FileReader reader = new FileReader("test.txt")) {
char ch = (char) reader.read();
System.out.println(ch);
} catch (IOException e) {
log.error("文件读取异常", e);
// 向上抛出自定义异常,触发事务回滚
throw new RuntimeException("文件操作失败", e);
}
}
七、业务开发最佳实践
- 工具类 / 底层 IO 方法 :使用
throws IOException抛出,不吞异常; - Service 业务层:捕获 IO 异常后包装为运行时异常抛出,配合事务回滚:
java
@Transactional(rollbackFor = Exception.class)
public void importData() {
try (BufferedReader br = Files.newBufferedReader(Paths.get("data.csv"))) {
String line;
while ((line = br.readLine()) != null) {
saveDb(line);
}
} catch (IOException e) {
log.error("导入文件失败", e);
// 包装为RuntimeException,触发事务回滚
throw new RuntimeException("文件读取失败", e);
}
}
- 禁止空 catch :不要只写 catch(IOException e){ } 吃掉异常,会丢失报错线索;
- 网络、文件、FTP、OSS 操作全部属于 IO,统一用
try-with-resources。
八、核心总结
- IOException 是受检异常,必须
throws或try-catch; - JDK7+ 优先
try-with-resources自动关闭流; - Spring 事务中,单纯抛出 IOException 不回滚,要么配置
rollbackFor=Exception.class,要么 catch 后转抛 RuntimeException; - 捕获后务必打印完整异常日志,便于排查。