Java事務(7)——使用Transactional註解

本系列上一篇文章中,我們講到了使用動態代理的方式完成事務處理,這種方式將service層的所有public方法都加入到事務中,這顯然不是我們需要的,需要代理的只是那些需要操作數據庫的方法。在本篇中,我們將講到如何使用Java註解(Annotation)來標記需要事務處理的方法。

 

  首先定義Transactional註解:

複製代碼
package davenkin.step6_annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional
{
}
複製代碼

 

  使用註解標記事務的基本原理爲:依然使用上一篇中講到的動態代理的方式,只是在InvocationHandler的invoke方法中,首先判斷被代理的方法是否標記有Transactional註解,如果沒有則直接調用method.invoke(proxied, objects),否則,先準備事務,在調用method.invoke(proxied, objects),然後根據該方法是否執行成功調用commit或rollback。定義TransactionEnabledAnnotationProxyManager如下:

複製代碼
package davenkin.step6_annotation;

import davenkin.step3_connection_holder.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TransactionEnabledAnnotationProxyManager
{
    private TransactionManager transactionManager;

    public TransactionEnabledAnnotationProxyManager(TransactionManager transactionManager)
    {

        this.transactionManager = transactionManager;
    }

    public Object proxyFor(Object object)
    {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new AnnotationTransactionInvocationHandler(object, transactionManager));
    }
}

class AnnotationTransactionInvocationHandler implements InvocationHandler
{
    private Object proxied;
    private TransactionManager transactionManager;

    AnnotationTransactionInvocationHandler(Object object, TransactionManager transactionManager)
    {
        this.proxied = object;
        this.transactionManager = transactionManager;
    }

    public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable
    {
        Method originalMethod = proxied.getClass().getMethod(method.getName(), method.getParameterTypes());
        if (!originalMethod.isAnnotationPresent(Transactional.class))
        {
            return method.invoke(proxied, objects);
        }

        transactionManager.start();
        Object result = null;
        try
        {
            result = method.invoke(proxied, objects);
            transactionManager.commit();
        } catch (Exception e)
        {
            transactionManager.rollback();
        } finally
        {
            transactionManager.close();
        }
        return result;
    }
}
複製代碼

 

  可以看到,在AnnotationTransactionInvocationHandler的invoke方法中,我們首先獲得原service的transfer方法,然後根據originalMethod.isAnnotationPresent(Transactional.class)判斷該方法是否標記有Transactional註解,如果沒有,則任何額外功能都不加,直接調用原來service的transfer方法;否則,將其加入到事務處理中。

 

  在service層中,我們只需將需要加入事務處理的方法用Transactional註解標記就行了:

複製代碼
package davenkin.step6_annotation;

import davenkin.BankService;
import davenkin.step3_connection_holder.ConnectionHolderBankDao;
import davenkin.step3_connection_holder.ConnectionHolderInsuranceDao;

import javax.sql.DataSource;

public class AnnotationBankService implements BankService
{
    private ConnectionHolderBankDao connectionHolderBankDao;
    private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;

    public AnnotationBankService(DataSource dataSource)
    {
        connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);
        connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);
    }

    @Transactional
    public void transfer(final int fromId, final int toId, final int amount)
    {
        try
        {
            connectionHolderBankDao.withdraw(fromId, amount);
            connectionHolderInsuranceDao.deposit(toId, amount);
        } catch (Exception e)
        {
            throw new RuntimeException();
        }
    }
}
複製代碼

 

  然後執行測試:

複製代碼
    @Test
    public void transferFailure() throws SQLException
    {
        TransactionEnabledAnnotationProxyManager transactionEnabledAnnotationProxyManager = new TransactionEnabledAnnotationProxyManager(new TransactionManager(dataSource));
        BankService bankService = new AnnotationBankService(dataSource);
        BankService proxyBankService = (BankService) transactionEnabledAnnotationProxyManager.proxyFor(bankService);

        int toNonExistId = 3333;
        proxyBankService.transfer(1111, toNonExistId, 200);

        assertEquals(1000, getBankAmount(1111));
        assertEquals(1000, getInsuranceAmount(2222));
    }
複製代碼

 

  測試運行成功,如果將AnnotationBankService中transfer方法的Transactional註解刪除,那麼以上測試將拋出RuntimeException異常,該異常爲transfer方法中我們人爲拋出的,也即由於此時沒有事務來捕捉異常,程序便直接拋出該異常而終止運行。在下一篇(本系列最後一篇)文章中,我們將講到分佈式事務的一個入門例子。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章