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

Java事务处理全解析(七)—— 像Spring一样使用Transactional注解(Annotation)的更多相关文章

  1. Java事务处理全解析(一)——Java事务处理的基本问题

    Java中的事务处理有多简单?在使用EJB时,事务在我们几乎察觉不到的情况下发挥着作用:而在使用Spring时,也只需要配置一个TransactionManager,然后在需要事务的方法上加上Tran ...

  2. Java事务处理全解析(六)—— 使用动态代理(Dynamic Proxy)完成事务

    在本系列的上一篇文章中,我们讲到了使用Template模式进行事务管理,这固然是一种很好的方法,但是不那么完美的地方在于我们依然需要在service层中编写和事务处理相关的代码,即我们需要在servi ...

  3. Java事务处理全解析(五)—— Template模式

    在本系列的上一篇文章中,我们讲到了使用TransactionManger和ConnectionHolder完成线程安全的事务管理,在本篇中,我们将在此基础上引入Template模式进行事务管理. Te ...

  4. Java事务处理全解析(二)——失败的案例

    在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例. BankService ...

  5. Java事务处理全解析(八)——分布式事务入门例子(Spring+JTA+Atomikos+Hibernate+JMS)

    在本系列先前的文章中,我们主要讲解了JDBC对本地事务的处理,本篇文章将讲到一个分布式事务的例子. 请通过以下方式下载github源代码: git clone https://github.com/d ...

  6. Java事务处理全解析(四)—— 成功的案例(自己实现一个线程安全的TransactionManager)

    在本系列的上一篇文章中我们讲到,要实现在同一个事务中使用相同的Connection对象,我们可以通过传递Connection对象的方式达到共享的目的,但是这种做法是丑陋的.在本篇文章中,我们将引入另外 ...

  7. Java事务处理全解析(三)——丑陋的案例

    在本系列的上一篇文章中,我们看到了一个典型的事务处理失败的案例,其主要原因在于,service层和各个DAO所使用的Connection是不一样的,而JDBC中事务处理的作用对象正是Connectio ...

  8. java事务处理全解析

    http://blog.csdn.net/huilangeliuxin/article/details/43446177

  9. 《Java面试全解析》505道面试题详解

    <Java面试全解析>是我在 GitChat 发布的一门电子书,全书总共有 15 万字和 505 道 Java 面试题解析,目前来说应该是最实用和最全的 Java 面试题解析了. 我本人是 ...

随机推荐

  1. 使用jQuery Mobile的注意事项(译)

    翻译编辑自:http://www.appnovation.com/blog/7-things-know-about-jquery-mobile-working-it 一.Android和IOS的内置键 ...

  2. html中input文本框,初始里边有文字提示,当点击时,文字消失,怎么设置?

    使用onfocus事件检查当前值,如果是默认值,就将value属性置空.如:<input type="text" value="请输入内容" onfocu ...

  3. Oracle 差异增量和累计增量备份

    网址: http://www.eygle.com/digest/2009/04/oracle_rman_incremental_backup.html 在rman增量备份中,有差异增量和累积增量的概念 ...

  4. iOS开发UI篇—Date Picker和UITool Bar控件简单介绍

    iOS开发UI篇—Date Picker和UITool Bar控件简单介绍 一.Date Picker控件 1.简单介绍: Date Picker显示时间的控件 有默认宽高,不用设置数据源和代理 如何 ...

  5. hdu 4747 Mex

    http://acm.hdu.edu.cn/showproblem.php?pid=4747 设我们输入的数组为 a[],我们需要从 1 到 n 遍历, 假设遍历到 i 时, 遍历的过程中用b[j]表 ...

  6. Linux学习 : 裸板调试 之 使用MMU

    MMU(Memory Management Unit,内存管理单元),操作系统通过使用处理器的MMU功能实现以下:1)虚拟内存.有了虚拟内存,可以在处理器上运行比实际物理内存大的应用程序.为了使用虚拟 ...

  7. 谈谈eclipse使用技巧二

    上节说道了怎么使用eclipse使您事半功倍.这节告诉您怎么用eclipse练成火眼金睛. ①借你一双火眼金睛让类的层次结构一目了然让你阅读代码如虎添翼 一个好的类的层次结构,让你的类的层次清晰明了, ...

  8. jquery Mobile弹出窗

    先创建一个窗体 <div data-role="popup" id="popupView" class="ui-content" da ...

  9. 【php常用】常用函数啥的

    1.intval()  把变量转换成整数类型 2.trim() 去除字符串两边空格or 加上参数是去除该参数 3.array_values()  函数返回一个包含给定数组中所有键值的数组,但不保留键名 ...

  10. 福州月赛2057 DFS

    题意:告诉你族谱,然后Q条查询s和t的关系,妈妈输出M,爸爸输出F: 题目地址:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=78233# ...