# spring-transactional **Repository Path**: wzk9261/spring-transactional ## Basic Information - **Project Name**: spring-transactional - **Description**: 深入了解 Spring 的 @Transactional 注解 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2019-12-20 - **Last Updated**: 2021-09-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # spring-transactional ## 案例简介 本案例主要用于深入了解 Spring 的事务注解`@Transactional`。 ## @Transactional 特性 1. `@Transactional`注解只能应用到 public 修饰符上,对其他修饰符不起作用,但不报错。 2. `@Transactional` 一般加到实现类或实现类方法上,不要加到接口或接口方法上。 3. `@Transactional`仅仅对 unchecked 异常进行事务异常回滚;如果是 checked 异常则不进行异常回滚。 - unchecked 异常一般为错误或运行时异常 - Error:Throwable 的子类。著名的 VirtualMachineError 是 Error 的子类之一,VirtualMachineError 还有 StackOverflowError、OutOfMemoryError 等子类。 - RuntimeException:Exception 的子类,而 Exception 同样是 Throwable 的子类。RuntimeException 下面包括 NPE,ClassCastException,ArithmaticException 等异常,这些错误的特点是不用被显式地抛出或捕获,开发者通过仔细检查代码可以避免这些异常。 - checked 异常则为编译过程中的异常 - 其他 Exception:除了 RuntimeException 之外其他的 Exception 的子类,包括 FileNotFoundException,IOException,SQLException 等,这些异常的特点是必须被代码抛出或捕获,否则编译都无法通过。 ## @Transactional 有效场景 1. 正常执行写操作 ```java @Override @Transactional public int updateById(Employee employee, Integer empId) { return employeeMapper.updateById(employee, empId); } ``` 2. unchecked 异常 - RuntimeException ```java @Override @Transactional public int updateByIdRuntimeException(Employee employee, Integer empId) { int effect = employeeMapper.updateById(employee, empId); System.out.println(1 / 0); return effect; } ``` 调用此方法,在执行此方法的语句`System.out.println(1 / 0);`时会抛出运行时异常中的算术异常(ArithmaticException),事务会回滚,数据库中的数据不会被改变。 - OutOfMemoryError 写一个类来制造堆内存溢出 ```java public class HeapOOM { static class OOMObject { } public static void mockHeapOOM() { List objects = new ArrayList<>(); while (true) { objects.add(new OOMObject()); } } } ``` 可以调用此类的静态方法 `mockHeapOOM` 不停地创建 `OOMObject` 对象并将其添加至数组中,而被数组强引用的对象无法被 GC 回收,堆内存很快被消耗完毕。 ```java @Override @Transactional public int updateByIdOOM(Employee employee, Integer empId) { int effect = employeeMapper.updateById(employee, empId); HeapOOM.mockHeapOOM(); return effect; } ``` 打开 IntelliJ IDEA 的 Edit Configuration,在 VM Options 一栏为调用 `updateByIdOOM` 方法的单元测试配置好 JVM 参数。 ```java -ea -Xmx10m -Xms5m -XX:+HeapDumpOnOutOfMemoryError ``` - `-ea` 可以打开断言机制 - `-Xmx10m -Xms5m` 表示堆最大可用值是 10M,堆初始值为 5M - `-XX:+HeapDumpOnOutOfMemoryError` 打印堆溢出报错信息 运行单元测试方法,控制台中报如下错误信息: ```java java.lang.OutOfMemoryError: GC overhead limit exceeded ``` 查看数据库,数据没有被改变,所以此时事务正常回滚。 ## @Transactional 无效场景 1. checked 异常 ```java @Override @Transactional public void updateByIdException(Employee employee, Integer empId) throws IOException { employeeMapper.updateById(employee, empId); throw new IOException("模拟 IO 错误"); } ``` 调用此方法,会在`throw new IOException("模拟 IO 错误")`时抛出模拟的错误,但是事务并不回滚,数据库中的数据发生了改变。这是因为 IOException 属于 checked 异常,`@Transactional` 默认情况下不对 checked 异常进行事务回滚。此时,想要在抛出 checked 异常时进行事务回滚,需要在 `@Transactional`后面配置上`rollBackFor`属性: ```java @Override @Transactional(rollbackFor = IOException.class) public void updateByIdException(Employee employee, Integer empId) throws IOException { employeeMapper.updateById(employee, empId); throw new IOException("模拟 IO 错误"); } ``` 这里配置为`rollbackFor = Exception.class`可以。 2. 被没有事务控制的方法调用 如果带有 `@Transactional`的方法 A 被另一个不带有`@Transactional`的方法 B 调用,那么调用 B 时 A 的`@Transactional`也会失效。 ```java @Override @Transactional public int updateByIdRuntimeException(Employee employee, Integer empId) { int effect = employeeMapper.updateById(employee, empId); System.out.println(1 / 0); return effect; } @Override public int updateByIdWithoutTransactional(Employee employee, Integer empId) { return updateByIdRuntimeException(employee, empId); } ``` 此时,调用 `updateByIdWithoutTransactional`,数据库的数据会被改变,这是由于事务不会因为在`updateByIdRuntimeException`中抛出 `ArithmaticException`而回滚。