readOnly :事务是否是只读的,默认是 false。对于只读查询,可以指定事务类型为 readonly,即只读事务。由于只读事务不存在数据的修改, 因此数据库将会为只读事务提供一些优化手段

rollbackFor :设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则事务回滚

noRollbackFor :设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚

rollbackForClassName :设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。

noRollbackForClassName :设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。

使用@Transactional需要注意的地方

  • @Transactional 只能应用到 public 方法才有效
  • 在默认配置中,Spring FrameWork 的事务框架代码只会将出现 runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常是 RuntimeException 或其子类,这样事务才会回滚(默认情况下 Error 也会导致事务回滚)。但是,在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。
  • 1、创建spring项目

    新建Spring Initializr项目

    配置pom.xml文件

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    

    创建entity包,创建对应数据库表的实体类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Entity
    @Table(name = "extra_ad")
    public class ExtraAd {
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name ="id",nullable = false)
        private Long id;
        @Basic
        @Column(name = "name",nullable = false)
        private String name;
        public ExtraAd(String name){
            this.name = name;
    

    创建dao包,创建操作数据库表的类的接口

    public interface ExtraAdDao extends JpaRepository<ExtraAd,Long> {
    

    创建exception包,自定义异常类

    public class CustomException extends Exception{
        public CustomException(String message){
            super(message);
    

    创建service包,定义服务接口

    public interface ISpringTransaction {
        // 主动捕获异常,事务不能回滚
        void CatchExceptionCanNotRollback();
        // 不是unchecked异常,事务不能回滚
        void NotRuntimeExceptionCanNotRollback() throws CustomException;
        // uncheck异常,事务可以回滚
        void RuntimeExceptionCanRollback();
        // 指定异常,事务可以回滚
        void AssignExceptionCanRollback() throws CustomException;
        // Rollback Only,事务可以回滚
        void RollbackOnlyCanRollback() throws CustomException;
        // 同一个类中,一个不标注事务的方法区调用了标注了事务的方法,事务会失效
        void NonTransactionalCanNotRollback();
    

    在service包下创建impl包,实现ISpringTransaction接口

    在test下新建测试类

    整体项目结构如图:

    2、创建测试数据库

    CREATE DATABASE IF NOT EXISTS `example`;
    CREATE TABLE IF NOT EXISTS `example`.`extra_ad` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
        `name` varchar(48) NOT NULL COMMENT '名称',
        PRIMARY KEY (`id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='额外的表';
    TRUNCATE TABLE extra_ad;
    

    对应的application.yml

    spring:
      profiles:
        active: dev
        open-in-view: false
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/example?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
        username: ****
        password: ****
        driver-class-name: com.mysql.cj.jdbc.Driver
        tomcat:
          max-active: 4
          min-idle: 2
          initial-size: 2
    spring:
      profiles: dev
    server:
      port: 8080
    spring:
      profiles: test
    server:
      port: 8081
    spring:
      profiles: prod
    server:
      port: 8082
    

    几种实践情况

    1、主动捕获异常,事务不能回滚

    @Override
    @Transactional
    public void CatchExceptionCanNotRollback() {
        try{
            extraAdDao.save(new ExtraAd("test1"));
            throw new RuntimeException();
        }catch (Exception ex){
            ex.printStackTrace();
    

    执行前,表中无记录

    @Test
    public void NotRuntimeExceptionCanNotRollback() throws CustomException{
        springTransaction.NotRuntimeExceptionCanNotRollback();
    并没有回滚,数据插入成功

    清空数据库,继续下一个

    2、不是unchecked异常,事务不能回滚

    @Override
    @Transactional
    public void NotRuntimeExceptionCanNotRollback() throws CustomException {
        try {
            extraAdDao.save(new ExtraAd("test2"));
            throw new RuntimeException();
        } catch (Exception ex){
            throw new CustomException(ex.getMessage());
    

    这里强制把RumtimeException转为CustomException,就不是unchecked异常

    没有回滚,成功插入

    清空数据库,继续下一条

    3、uncheck异常,事务可以回滚

    @Override
    @Transactional
    public void RuntimeExceptionCanRollback() {
        extraAdDao.save(new ExtraAd("test3"));
        throw new RuntimeException();
    

    RuntimeException是uncheck异常,发生回滚,数据并没有插入成功

    4、指定异常,事务可以回滚

    @Override
    @Transactional(rollbackFor = {CustomException.class})
    public void AssignExceptionCanRollback() throws CustomException {
        try{
            extraAdDao.save(new ExtraAd("test4"));
            throw new RuntimeException();
        }catch (Exception ex){
            throw new CustomException(ex.getMessage());
    参数rollbackFor指定了CustomException异常类型,并且抛出了该异常,事务回滚,数据插入失败

    5、Rollback Only,事务处理失败

    @Transactional
    public void oneSaveMethod(){
        extraAdDao.save(new ExtraAd("test5"));
    @Override
    @Transactional
    public void RollbackOnlyCanRollback() throws CustomException {
        oneSaveMethod();
        try{
            extraAdDao.save(new ExtraAd());
        }catch (Exception ex){
            ex.printStackTrace();
            // throw ex;
    
    org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.ad.extraad.entity.ExtraAd.name; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.ad.extraad.entity.ExtraAd.name
    

    由于字段name要求不为空,extraAdDao.save(new ExtraAd())运行出错

    org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
    

    本方法中调用了标记为事务的方法oneSaveMethod(),多个事务合为一个事务,只有全部正确执行完成才会提交事务,由于extraAdDao.save(new ExtraAd())运行出错,事务被标记为rollback-only,导致事务回滚,数据插入失败

    6、同一个类中,一个不标注事务的方法调用了标注了事务的方法,事务会失效

    @Transactional
    public void anotherOneSaveMethod(){
        extraAdDao.save(new ExtraAd("test6"));
        throw new RuntimeException();
    @Override
    public void NonTransactionalCanNotRollback() {
        anotherOneSaveMethod();
    

    可以看到,事务并没有回滚,数据成功插入

    运行中抛出的异常

    java.lang.RuntimeException
    	at com.ad.extraad.service.impl.SpringTransactionImpl.anotherOneSaveMethod(SpringTransactionImpl.java:89)
    	at com.ad.extraad.service.impl.SpringTransactionImpl.NonTransactionalCanNotRollback(SpringTransactionImpl.java:93)
    	at com.ad.extraad.service.impl.SpringTransactionImpl$$FastClassBySpringCGLIB$$3ffea55.invoke(<generated>)
    	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
    	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
    	at com.ad.extraad.service.impl.SpringTransactionImpl$$EnhancerBySpringCGLIB$$175ae292.NonTransactionalCanNotRollback(<generated>)
    	at com.ad.extraad.service.TransactionTest.NonTransactionalCanNotRollback(TransactionTest.java:53)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    

    从抛出的异常可以看到,首先,我们拿到的是代理对象,调用 NonTransactionalCanNotRollback 方法。但是 NonTransactionalCanNotRollback 方法在调用 anotherOneSaveMethod 的时候却是原始对象的 anotherOneSaveMethod。所以,这里的调用根本就没有事务的存在,导致事务失效,就更加不存在回滚了.

    7、不同类中,一个不标注事务的方法调用了标注了事务的方法,事务生效

    可以看到,由于异常是uncheck类型,事务发生了回滚,数据并没有插入成功

    java.lang.RuntimeException
    	at com.ad.extraad.service.impl.SpringTransactionImpl.anotherOneSaveMethod(SpringTransactionImpl.java:89)
    	at com.ad.extraad.service.impl.SpringTransactionImpl$$FastClassBySpringCGLIB$$3ffea55.invoke(<generated>)
    	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
    	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
    

    从抛出的异常可以看到,首先,我们拿到的是代理对象,再去调用anotherOneSaveMethod。所以,这就有事务了,即事务不会失效。

    bkpp976 Java后端工程师
    粉丝