Java事务之七——使用Transactional注解

  在本系列上一篇文章中,我们讲到了使用动态代理的方式完成事务处理,这种方式将 service 层的所有 public 方法都加入到事务中,这显然不是我们需要的,需要代理的只是那些需要操作数据库的方法。在本篇中,我们将讲到如何使用 Java 注解(Annotation)来标记需要事务处理的方法。

 

  这是一个关于 Java 事务处理的系列文章,请通过以下方式下载 github 源代码:

git clone https://github.com/davenkin/java_transaction_workshop.git

 

  首先定义 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;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> TransactionEnabledAnnotationProxyManager(TransactionManager transactionManager)
{

    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.transactionManager =<span style="color: rgba(0, 0, 0, 1)"> transactionManager;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Object proxyFor(Object object)
{
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AnnotationTransactionInvocationHandler(object, transactionManager));
}

}

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

AnnotationTransactionInvocationHandler(Object object, TransactionManager transactionManager)
{
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.proxied =<span style="color: rgba(0, 0, 0, 1)"> object;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.transactionManager =<span style="color: rgba(0, 0, 0, 1)"> transactionManager;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> Object invoke(Object proxy, Method method, Object[] objects) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable
{
    Method originalMethod </span>=<span style="color: rgba(0, 0, 0, 1)"> proxied.getClass().getMethod(method.getName(), method.getParameterTypes());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!originalMethod.isAnnotationPresent(Transactional.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">))
    {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> method.invoke(proxied, objects);
    }

    transactionManager.start();
    Object result </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
    {
        result </span>=<span style="color: rgba(0, 0, 0, 1)"> method.invoke(proxied, objects);
        transactionManager.commit();
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
    {
        transactionManager.rollback();
    } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)">
    {
        transactionManager.close();
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> 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;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> AnnotationBankService(DataSource dataSource)
{
    connectionHolderBankDao </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ConnectionHolderBankDao(dataSource);
    connectionHolderInsuranceDao </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ConnectionHolderInsuranceDao(dataSource);
}

@Transactional
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> transfer(<span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> fromId, <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> toId, <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> amount)
{
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
    {
        connectionHolderBankDao.withdraw(fromId, amount);
        connectionHolderInsuranceDao.deposit(toId, amount);
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
    {
        </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> 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);
    </span><span style="color: rgba(0, 0, 255, 1)">int</span> toNonExistId = 3333<span style="color: rgba(0, 0, 0, 1)">;
    proxyBankService.transfer(</span>1111, toNonExistId, 200<span style="color: rgba(0, 0, 0, 1)">);

    assertEquals(</span>1000, getBankAmount(1111<span style="color: rgba(0, 0, 0, 1)">));
    assertEquals(</span>1000, getInsuranceAmount(2222<span style="color: rgba(0, 0, 0, 1)">));
}</span></span></pre>

 

  测试运行成功,如果将 AnnotationBankService 中 transfer 方法的 Transactional 注解删除,那么以上测试将抛出 RuntimeException 异常,该异常为 transfer 方法中我们人为抛出的,也即由于此时没有事务来捕捉异常,程序便直接抛出该异常而终止运行。在下一篇(本系列最后一篇)文章中,我们将讲到分布式事务的一个入门例子。