手寫源碼(一):本身實現Spring事務

手寫Spring事務

Spring事務分爲聲明式事務(註解或包掃描)和編程式(在代碼裏提交或回滾)事務,聲明式事務就是在編程式事務的基礎上加上AOP計數進行包裝
這個工程爲了實驗事務的回滾,使用用了數據庫,使用了jdbc模板鏈接數據庫 ,數據庫鏈接池配置再RootConfig裏
我導入的Maven依賴以下java

<dependencies>
        <!-- 引入Spring-AOP等相關Jar -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_2</version>
        </dependency>
        <!--mysql鏈接驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <!--鏈接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--測試-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

複製代碼

配置類以下,用於代替有些過期的XML配置Spring
mysql

@Configuration
@ComponentScan(basePackages = {"com.libi"})
@EnableAspectJAutoProxy
public class RootConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/sms?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
複製代碼

須要加入事務的方法以下userDao是會操做數據的,在中間的間隔會拋出異常spring

@Service
 public class UserServiceImpl implements UserService {
     @Autowired
     private UserDao userDao;
     public void add() {
         userDao.add("test001","1233321");
         System.out.println("中間的間隔,且出現異常");
         int i = 1 / 0;
         userDao.add("test002","135365987");
     }
 }
複製代碼

這時只會插入test001的語句,test002不會插入成功。
sql

編程式事務

這時咱們封裝一個事務工具數據庫

@Component
@Scope("prototype")
public class TransactionUtils {
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    private TransactionStatus status;
    
    /** 開啓事務*/
    public TransactionStatus begin() {
        //使用默認的傳播級別
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
        return transaction;
    }

    /** 提交事務 須要傳入這個事務狀態*/
    public void commit() {
        dataSourceTransactionManager.commit(status);
    }

    /**回滾事務 須要傳入這個事務狀態*/
    public void rollBack() {
        //獲取當前事務,若是有,就回滾
        if (status != null) {
            dataSourceTransactionManager.rollback(status);
        }
    }
}
複製代碼

再這樣使用,修改add方法編程

public void add() {
        TransactionStatus begin = null;
        try {
            begin = transactionUtils.begin();
            userDao.add("test001", "1233321");
            System.out.println("中間的間隔,且出現異常");
            int i = 1 / 0;
            userDao.add("test002", "135365987");
            transactionUtils.commit();
        } catch (Exception e) {
            e.printStackTrace();
            transactionUtils.rollBack();
        }
    }
複製代碼

聲明式事務

咱們使用AOP編程把剛剛的事務工具封裝一下安全

@Component
@Aspect
public class AopTransaction {
    @Autowired
    private TransactionUtils transactionUtils;

    @Around("execution(* com.libi.service.UserService.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("開啓事務");
        proceedingJoinPoint.proceed();
        System.out.println("提交事務");
        transactionUtils.commit();
    }

    @AfterThrowing("execution(* com.libi.service.UserService.add(..))")
    public void afterThrowing() {
        System.out.println("回滾事務");
        //獲取當前事務,直接回滾
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    
}
複製代碼

而後清空原來的方法裏全部的try代碼,讓他回到最初的狀態(不能捕獲異常,否者出現異常後不能被異常通知捕獲到,致使事務不生效bash

註解式事務

在Spring裏已經幫咱們實現類註解事務,須要在配置類裏添加下面的註解來開啓註解事務的支持框架

@EnableTransactionManagement
複製代碼

而後註釋掉咱們上次的AOP註解,使用@Transactional(rollbackFor = Exception.class)的註解開啓這個方法的事務,rollbackFor標識須要回滾的異常類,整個方法以下工具

@Transactional(rollbackFor = Exception.class)
    public void add() {
        userDao.add("test001", "1233321");
        System.out.println("中間的間隔,且出現異常");
        int i = 1 / 0;
        userDao.add("test002", "135365987");
    }
複製代碼

這樣也能夠實現這個方法的事務。固然,這個方法裏也不能捕獲異常,這樣仍然會致使沒法觸發異常通知而致使事務無效
咱們就以這種效果做爲模板手寫事務的框架


具體步驟

  • 定義註解
/**
 * @author libi
 * 本身實現的事務註解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtTransaction {
    
}
複製代碼
  • 封裝手動事務(使用原來的TransactionUtils類)
  • 使用AOP掃描規定包下的註解
    • 在AOP上封裝找到註解而且加上註解的操做
@Component
@Aspect
public class AopAnnotationTransaction {
    @Autowired
    private TransactionUtils transactionUtils;
    /**這邊規定掃描service下的全部方法*/
    @Around("execution(* com.libi.service.*.*(..))")
        //獲取方法上的註解,這裏把獲取註解的方法單獨提出來了
        ExtTransaction extTransaction = getExtTransaction(proceedingJoinPoint);

        TransactionStatus status = null;
        if (extTransaction != null) {
            //若果有事務,開啓事務
            System.out.println("開啓事務");
            status = transactionUtils.begin();
        }
        //調用代理目標方法
        proceedingJoinPoint.proceed();
        if (status != null) {
            //提交事務
            System.out.println("提交事務");
            transactionUtils.commit();
        }
    }

    /**事務的異常通知*/
    @AfterThrowing("execution(* com.libi.service.*.*.*(..))")
    public void afterThrowing() {
        System.out.println("回滾事務");
       transactionUtils.rollBack();
    }

    /**獲取方法上的註解*/
    private ExtTransaction getExtTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException {
        //獲取代理對象的方法
        String methodName = proceedingJoinPoint.getSignature().getName();
        Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
        Class[] parameterTypes = ((MethodSignature) (proceedingJoinPoint.getSignature())).getParameterTypes();
        Method targetMethod = targetClass.getMethod(methodName, parameterTypes);
        //獲取方法上的註解
        return targetMethod.getAnnotation(ExtTransaction.class);
    }
}
複製代碼

還要注意的是,TransactionUtils類仍然須要時多例的,否則會出現線程安全問題

事務傳播行爲

  • 什麼是傳播行爲(Propagation) :事務的傳播行爲產生在調用事務中,也就是說當小個事務嵌套在大事務裏時,會發生怎樣的行爲
  • 傳播行爲的種類
    • PROPAGATION_REQUIRED—若是當前有事務,就用當前事務,若是當前沒有事務,就新建一個事務。這是最多見的選擇。(若是大的方法有事務,那麼須要事務的小方法就加入到這個事務裏去,若是大方法沒有事務,就建立事務
    • PROPAGATION_SUPPORTS--支持當前事務,若是當前沒有事務,就以非事務方式執行。//(若是外層方法沒有事務,就會以非事務進行執行。這樣至關於默認沒有事務
    • PROPAGATION_MANDATORY--支持當前事務,若是當前沒有事務,就拋出異常。
    • PROPAGATION_REQUIRES_NEW--新建事務,若是當前存在事務,把當前事務掛起(互不影響,運行到小事務時暫停大事務)。
    • PROPAGATION_NOT_SUPPORTED--以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
    • --- 若是當前有事務,就是以非事務進行執行
    • PROPAGATION_NEVER--以非事務方式執行,若是當前存在事務,則拋出異常。
相關文章
相關標籤/搜索