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 方法中我们人为抛出的,也即由于此时没有事务来捕捉异常,程序便直接抛出该异常而终止运行。在下一篇(本系列最后一篇)文章中,我们将讲到分布式事务的一个入门例子。