1. 程式人生 > >手寫Spring註解事務(利用AOP技術 + 註解 + Spring程式設計式事務)

手寫Spring註解事務(利用AOP技術 + 註解 + Spring程式設計式事務)

1.參考下面的文章搭建一個無事務管理的SSM操作資料庫的框架

      Spring 使用Druid資料來源 整合 Mybatis

2.AOP技術參考

      AOP技術應用實現

3.第一步首先實現Spring程式設計式事務

       1) 建立事務管理類工具,即手動開啟事務,手動提交事務,手動回滾事務的方法

package com.roger.core.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Component
@Scope("prototype")//設定成原型狀態,避免執行緒安全問題
public class TransactionUtil {

    private TransactionStatus transactionStatus = null;

    @Autowired
    private DataSourceTransactionManager transactionManager;


    public void begin(){
        System.out.println("方法上有事務註解,使用手動的方式開啟事務...");
        transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
    }

    public void commit(){
        if(transactionStatus != null){
            System.out.println("提交事務...");
            transactionManager.commit(transactionStatus);
        }
    }

    public void rollback(){
        if(transactionStatus != null) {
            System.out.println("回滾事務...");
            transactionManager.rollback(transactionStatus);
        }
    }
}

         2) 建立程式設計式事務管理的切面類

package com.roger.core.aspect;

import com.roger.core.utils.TransactionUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
//一定要把切面交給Spring管理
//在測試宣告式事務的時候,需要從Spring的容器中收回程式設計式事務的切面
//@Component
@Aspect
public class ProgramTransactionAspect {

    @Autowired(required = false)
    private TransactionUtil transactionUtil;

    @Pointcut("execution(* com.roger.biz.service.impl..*.*(..))")
    public void addTransaction(){

    }

    //異常通知:給新增事務的方法回滾事務,當方法丟擲異常時
    @AfterThrowing("addTransaction()")
    public void rollbackTransaction(){
        //獲取當前事務,然後回滾
        transactionUtil.rollback();
    }

    //環繞通知:給需要新增事務的方法,手動開啟事務和提交事務
    @Around("addTransaction()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable{
        transactionUtil.begin();
        joinPoint.proceed();
        transactionUtil.commit();
    }
}

     3).建立測試介面以及測試介面實現類

package com.roger.biz.service;

import com.roger.core.model.User;

public interface UserService {

    void save(User user);
}
package com.roger.biz.service.impl;

import com.roger.biz.dao.UserDao;
import com.roger.biz.service.UserService;
import com.roger.core.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired(required = false)
    private UserDao userDao;

    @Override
    public void save(User user) {
        userDao.insert(user);
        //利用最簡單的構造異常來驗證事務的回滾操作
        //通過回滾操作來和事務提交作對比
       // int i = 1 / 0 ;
    }
}

    4.建立junit4測試類 --測試基類

package com.roger;

import com.roger.core.config.DataSourceConfig;
import com.roger.core.config.SpringConfig;
import com.roger.core.config.mybatis.SqlSessionConfig;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DataSourceConfig.class, SqlSessionConfig.class,SpringConfig.class})
public class SpringBaseTestSuit {

}

    5.具體測試介面的junit4測試類

package com.roger.biz.service.impl;

import com.roger.SpringBaseTestSuit;
import com.roger.biz.service.UserService;
import com.roger.core.model.User;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import static org.junit.Assert.*;

public class UserServiceImplTest extends SpringBaseTestSuit {

    @Autowired(required = false)
    private UserService userService;

    @Test
    public void save() {
        User user = new User();
        user.setUserName("Jackson");
        user.setAge(38);
        user.setPhone("15498756489");
        userService.save(user);
    }
}

      6.通過觀察資料庫操作表的記錄來觀察結果,這裡沒有寫太多的測試方法,來直接用Junit驗證測試結果

4.自定義事務註解-非常簡單的,只使用於方法級別的

package com.roger.core.annotaion;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomTransactional {

    String value() default "";
}

5.改造程式設計式事務的切面類-使其適應註解方式

package com.roger.core.aspect;

import com.roger.core.annotaion.CustomTransactional;
import com.roger.core.utils.TransactionUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

////在測試程式設計式事務的時候,需要從Spring的容器中收回宣告式事務的切面
@Component//一定要把切面交給Spring管理
@Aspect
public class DeclareTransactionAspect {

    @Autowired(required = false)
    private TransactionUtil transactionUtil;

    @Pointcut("execution(* com.roger.biz.service.impl..*.*(..))")
    public void addTransaction() {

    }

    //異常通知:給新增事務的方法回滾事務,當方法丟擲異常時
    @AfterThrowing("addTransaction()")
    public void rollbackTransaction() {
        transactionUtil.rollback();
    }

    //環繞通知:給需要新增事務的方法,手動開啟事務和提交事務
    @Around("addTransaction()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> targetCls = joinPoint.getTarget().getClass();
        //獲取即將要執行方法
        Method method = getInvokedMethod(targetCls, joinPoint);
        if (method == null) {
            joinPoint.proceed();
            return;
        }
        //判斷執行方法是否有事務註解
        Annotation customTransAnno = method.getAnnotation(CustomTransactional.class);
        if (customTransAnno == null) {
            System.out.println("方法上沒有事務註解,直接開始執行方法...");
            joinPoint.proceed();
            return;
        }

        transactionUtil.begin();
        joinPoint.proceed();

        transactionUtil.commit();
    }

    private Method getInvokedMethod(Class targetCls, ProceedingJoinPoint pJoinPoint) {
        List<Class<? extends Object>> clazzList = new ArrayList<>();
        Object[] args = pJoinPoint.getArgs();
        for (Object arg : args) {
            clazzList.add(arg.getClass());
        }

        Class[] argsCls = (Class[]) clazzList.toArray(new Class[0]);

        String methodName = pJoinPoint.getSignature().getName();
        Method method = null;
        try {
            method = targetCls.getMethod(methodName, argsCls);
        } catch (NoSuchMethodException e) {
            //不做任何處理,這個切面只處理事務相關邏輯
            //其他任何異常不在這個切面的考慮範圍
        }
        return method;
    }
}

6.github程式碼下載