文章目录
AOP
Spring另一核心技术是AOP,所谓AOP就是面向切面编程,它能够在不修改源代码的基础上,对代码的功能做出扩展。所以它的优势是能够降低了代码的重复率,提高了开发效率,并且容易维护。
而AOP的底层是通过Spring 提供的动态代理实现的。在运行期间,Spring通过动态代理技术动态生成代理对象,代理对象方法执行时进行增强功能的介入,然后再去调用目标对象方法,从而完成功能的增强。
而实现动态代理,主要有2种技术:
-
JDK — 基于接口的动态代理,对应的处理步骤为:
- 创建目标类Target,以及目标接口TargetInterface
- 创建切面类Advice,来封装增强的方法
- 通过Proxy调用静态方法newProxyInstance(arg1,arg2,arg3),来创建代理对象,其中这个方法中传递的参数为arg1为目标类的类加载器,arg2则是相同目标对象的字节码对象数组,arg3则是InnvocationHandler类,并且重写Innovaction的方法invoke,从而添加增强的方法,实现目标方法的增强。
对应的代码为:
public class Advice { public void beforeAdd(){ System.out.println("前置增强......."); } public void afterAdd(){ System.out.println("后置增强........"); } } public class Target implements TargetInterface { @Override public void save() { System.out.println("target save is running....."); } } public interface TargetInterface { void save(); } //测试类 public class ProxyTest { public static void main(String[] args) { final Target target = new Target(); //这个对象需要给final修饰,否则在InvocationHandler中调用的时候发生报错 final Advice advice = new Advice(); //创建代理对象 TargetInterface proxy = (TargetInterface)Proxy.newProxyInstance( target.getClass().getClassLoader(),//目标对象的类加载器 target.getClass().getInterfaces(),//目标对象相同的字节码对象组 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //对方法进行增强,在方法之前运行 advice.beforeAdd(); Object invoke = method.invoke(target, args);//执行target对象中的参数为args的方法 //对方法进行增强,方法执行完毕之后运行 advice.afterAdd(); return invoke; } } ); proxy.save(); } }
-
cglib – 基于父类的动态代理,对应的步骤为:
-
导入相关的依赖spring-context到pom.xml,因为spring-context中含有spring-core,而core包下面含有cglib这个包。
-
创建目标类Target
-
创建切面类Advice,封装增强的方法
-
创建增强器Enhancer对象
-
增强器调用setSuperClass(),来设置父类(目标对象)
-
增强器调用setCallBack()方法,设置回调,其中传递的参数为MethodInterceptor,并且重写这个参数的方法intercept,这样当代理对象调用对应的方法的时候,都会被这个方法拦截,从而实现方法的增强。
-
增强器调用create方法,从而创建代理对象
对应的代码为:
public class Advice { public void beforeAdd(){ System.out.println("前置增强......."); } public void afterAdd(){ System.out.println("后置增强........"); } } public class Target implements TargetInterface { @Override public void save() { System.out.println("target save is running....."); } } //测试类 public class ProxyTest { public static void main(String[] args) { final Target target = new Target(); final Advice advice = new Advice();//这时候的target,advice都需要被final修饰,否则在下面重写intercept方法的时候,就会发生报错 //1、创建增强器 Enhancer enhancer = new Enhancer(); //2、增强器设置父类(目标) enhancer.setSuperclass(target.getClass()); //3、增强器设置回调 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //方法执行之前,执行增强代码 advice.beforeAdd(); Object invoke = method.invoke(target, args); //方法执行完毕之后,执行增强代码 advice.afterAdd(); return invoke; } }); //4、创建代理对象 Target proxy = (Target)enhancer.create(); proxy.save(); } }
-
所以在明白上面AOP的底层实现之后,那么我们利用spring进行AOP操作的时候,而基于JDK还是基于cglib,则是判断目标对象是否有实现接口,从而判断AOP的底层技术是基于JDK还是基于cglib.在讲解如何实现AOP操作之前,我们需要先了解一些小知识:
- 连接点(JoinPoint): 目标类中所有的方法都是连接点
- 切点: 目标类中被调用的方法,就是切点.因为在上面的底层实现中,代理对象都对目标对象的方法都进行了增强,而代理对象执行的方法,就会真正的执行了增强功能,所以这时候正在执行的方法就是切点,而未被调用的方法则是连接点
- 通知(增强): 就是Advice类中封装的方法,用于增强作用的。
- 切面:切点 + 通知。
- 织入:生成代理对象的过程,个人理解是最终生成切面的过程。
Spring实现AOP操作,同样有2种方式来实现:
-
xml来实现AOP操作,对应的步骤为:
-
导入相关的依赖spirng-context,aspectjweaver,以及在整合junit进行测试的时候,需要导入spring-test,junit依赖.所以对应的依赖为:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.4</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
-
创建目标类Target,目标接口TargetInterface
-
创建切面类MyAspect
-
将这些类添加到Spring容器中。即在applicationContext.xml中进行配置。
-
这时候经过了第3步,仅仅时将Advice类变成了Spring容器的一个Bean对象而已,Spring并不知道他就是切面类(封装增强方法的类),所以这时候我们还需要通过aop命名空间来声明切面,其中aop声明切面的代码如下所示:
<aop:config> <aop:aspect ref="myAspect"> <!--ref就是我们的切面类对应的Bean的id--> <aop:before method="before" pointcut="execution(xxxxxxxx)"/> </aop:aspect> </aop:config>
其中
<aop:before method="before" pointcut="execution(xxxxxxxx)"/>
这一串中是说明切面类中的哪个方法是目标对象的哪一个方法(切点)的增强方法.其中aop:before
是用来说明增强的类型是前置通知,即在切点这个方法执行之前执行的,method
说明增强的方法是切面类中的哪一个方法,pointcut则是说明切点是哪一个类中的哪一个方法。增强的类型有
通知(增强)类型 作用 aop:before (前置通知) 在目标方法执行之前执行的增强方法 aop:afterReturning(后置通知) 在目标方法执行之后,方法返回之后才执行的增强方法 aop:around(环绕通知) 在目标方法执行前后执行这个增强方法,所以我们需要在切面类的环绕方法中传递参数ProceedingJoinPoint,表示正在运行的连接点,也即切点。然后通过这个ProceedingJoinPoint参数调用proceed方法,来执行切点这个方法。这时候如果在这个方法中提示不存在ProceedingJoinPoint,说明导入的依赖aspectjweaver的版本不太符合,可以尝试导入1.8.2版本的 aop:afterThrowing(异常通知) 在目标方法抛出异场之后执行这个增强方法 aop:after(最终通知) 不管目标方法有没有抛出异常,都会执行这个增强方法 而pointcut的值则是一个切点表达式,用来说明当前的增强方法是哪一个切点的增强方法。因为可能是多个切点的增强方法,所以用一个表达式来实现。对应的格式为
execution([修饰符] 方法的返回值 包名.类名.方法名(方法参数))
其中修饰符可以省略,并且方法的返回值,包名,类名,方法名可以用
*
表示任意,而方法参数可以使用..
来表示任意个参数。例如execution(* com.demo.*.*(..))
表示的切点是com.demo包下面的所有类的所有方法,如果方法的返回值是某一个类型,那么表示是com.demo包下面的所有类的返回值为某一类型的所有方法。注意的是,如果包和类名之间使用
..
分隔,那么表示的是对应包以及子包下的某一个类的方法。 -
利用spring整合junit来进行测试
对应的代码为:
public class MyAspect { public void before(){ System.out.println("这是一个前置方法.........."); } public void afterReturning(){ System.out.println("这是一个后置方法..........."); } public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕方法中切点方法执行之前........"); Object proceed = joinPoint.proceed();//执行切点方法 System.out.println("环绕方法中切点方法执行之后........"); return proceed; } public void afterThrowing(){ System.out.println("异常抛出之后执行的增强方法........."); } public void after(){ System.out.println("这是最终方法,不管是否抛出异常,都会执行........."); } } public class Target implements TargetInterface { @Override public void save() { System.out.println("切点方法save() is running......."); int a = 1 / 0; } } public interface TargetInterface { void save(); } //测试类:spring整合junit来进行测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application_anno_Context.xml") public class AnnoProxyTest { @Autowired private TargetInterface target; @Test public void test(){ target.save(); } }
对应的appicationContext代码:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="day7.demo"/> <!--配置目标方法--> <bean id="target" class="day7.demo.spring_proxy.Target"></bean> <!--配置切面类(也即增强方法的类),即将切面类添加到Spring MVC容器中--> <bean id="myAspect" class="day7.demo.spring_proxy.MyAspect"></bean> <!--配置切面,用来说明哪些类是切面,并且哪些切点将会被哪些方法增强--> <aop:config> <aop:aspect ref="myAspect"> <!--声明myAspect是一个切面类--> <!--如果切点表达式有多个相同,那么可以抽取出来,对应的格式为: <aop:pointcut id="ddd" expression="xxx"/> --> <!-- <aop:before method="before" pointcut="execution(public void day7.demo.spring_proxy.Target.save())"></aop:before> <aop:after-returning method="afterReturning" pointcut="execution(public void day7.demo.spring_proxy.Target.save())"></aop:after-returning> <aop:after-throwing method="afterThrowing" pointcut="execution(* day7.demo.spring_proxy.*.*(..))"></aop:after-throwing> <aop:after method="after" pointcut="execution(* day7.demo.spring_proxy.*.*(..))"></aop:after> <aop:around method="around" pointcut="execution(* day7.demo.spring_proxy.*.*(..))"/> --> <aop:pointcut id="pointcut_id" expression="execution(* day7.demo.spring_proxy.*.*(..))"/> <aop:before method="before" pointcut-ref="pointcut_id"></aop:before> <aop:after-returning method="afterReturning" pointcut-ref="pointcut_id"></aop:after-returning> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut_id"></aop:after-throwing> <aop:after method="after" pointcut-ref="pointcut_id"></aop:after> <aop:around method="around" pointcut-ref="pointcut_id"/> </aop:aspect> </aop:config> </beans>
值得注意的是,测试类ProxyTest中,不可以导入spring容器中的targetbean对象赋值给Target类型的target对象,否则就会发生报错
Unsatisfied dependency expressed through field 'target'; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'target' is expected to be of type 'day7.demo.spring_proxy.Target' but was actually of type 'com.sun.proxy.$Proxy18'
,大致意思是target对象本来希望的是我们的spring容器中的对象,但是实际上却是com.sun.proxy.$Proxy18的。因为我们使用的是自定义的类Target,并且在applicationContext.xml中配置的class也是正确的,并且在applicationContext中只有一个Target类型的Bean对象,所以利用@Autowired注解获取到的Target类型的Bean对象必然只有一个,所以可以排除了同一个类型的Bean对象重复的情况。
而这里发生报错的原因,是因为Java中会优先考虑接口,而不是直接创建对象来引用.所以需要将Target这个类型的Bean对象赋值给TargetIntegerface,也即上面程序所写的.
对应为什么优先考虑接口,可以参考以下文章:
-
-
注解开发来实现AOP操作
在明白通过xml来实现AOP操作之后,通过注解开发实现AOP操作就更加简单了,对应的代码为:
-
导入相关的依赖(和上面xml实现AOP操作的依赖是一样的)
-
创建目标类Target,目标接口TargetInterface
-
创建切面类MyAspect
-
利用@Component注解,从而将目标类Target,切面类MyAspect添加到Spring容器中。之所以使用的是@Component注解,是因为这些类并没有在controller层,也没有在dao层,service层,所以使用的是注解@Componenty,将其添加道spring容器中。
-
利用注解@Aspect,从而说明MyAspect是一个切面类
-
要说明切面类中的方法是什么类型的通知,那么就需要使用对应的注解即可,例如@Before说明这个增强的方法是一个前置方法,其余同理,所以对应的注解有5中:@Before(说明修饰的方法是前置通知的注解),@AfterReturning(后置通知),@AfterThrowing(异常通知),@After(最终通知,@Around(环绕通知)
其中这个注解的值是一个切点表达式,用来说明这个增强的方法对应的是哪一些切点。
-
配置application_anno_Context.xml文件,从而进行组件扫描,同时利用aop命名空间
<aop:aspectj-autoproxy/>
,来说明aop自动代理,否则,如果没有这一步,仅仅依靠组件扫描,那么即使切面类中使用了注解@Aspect注解,那么Spring也不知道对应的类是一个切面类。
对应的代码为:
application_anno_Context.xml文件代码为:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--组件扫描--> <context:component-scan base-package="day7.demo.anno_proxy"/> <!--aop自动代理--> <aop:aspectj-autoproxy/> </beans>
目标类以及切面类以及测试类代码:
@Component("myAspect") //将当前这个类添加到Spring MVC容器中 @Aspect //将当前这个类标记为切面类 public class MyAspect { @Before("execution(* day7.demo.anno_proxy.*.*(..))") //前置增强 public void before(){ System.out.println("这是一个前置方法.........."); } @AfterReturning("MyAspect.pointCut()") public void afterReturning(){ System.out.println("这是一个后置方法..........."); } @Around("MyAspect.pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕方法中切点方法执行之前........"); Object proceed = joinPoint.proceed();//执行切点方法 System.out.println("环绕方法中切点方法执行之后........"); return proceed; } @AfterThrowing("MyAspect.pointCut()") public void afterThrowing(){ System.out.println("异常抛出之后执行的增强方法........."); } @After("MyAspect.pointCut()") public void after(){ System.out.println("这是最终方法,不管是否抛出异常,都会执行........."); } @Pointcut("execution(* day7.demo.anno_proxy.*.*(..))") //使用@Pointcut,来提取pointcut public void pointCut(){ } } @Component("target") public class Target implements TargetInterface { @Override public void save() { System.out.println("切点方法save() is running......."); } } public interface TargetInterface { void save(); }
其中在切面类中,使用@Pointcut,来提取切点表达式,这样就可以使得如果有多个切点表达式相同的时候,就可以直接使用@Pointcut修饰的方法了。
-
Spring 的事务控制
如果在正常情况下,我们需要通过connection对象调用setAutoCommited(false),从而取消了自动提交事务,这样我们就已经开启了事务。当事务完成的时候,调用commit方法提交事务;当执行过程中出现了错误或者其他情况,我们可以调用rollback方法来回滚事务,将状态回滚到了开启事务前的状态。
但是在Spring中,这些代码可以交给spring来处理。spring要实现事务,它的底层技术使用的是AOP,之所以这样说,是因为我们开启事务去执行某个业务的时候,实际上就是对这个业务的基础上进行了增强,从而可以知道spring的事务控制底层技术是AOP。
所以,利用xml来实现spring的事务控制的基本步骤为:
- 导入相关的依赖
- 创建业务代码,这里我们以转账为例,所以需要创建AccounDao接口以及AccountDaoImpl,AccountService,AccountServiceImple类
- 在配置文件applicationContext.xml中,将上面的业务类添加到spring容器中
- 在配置文件applicationContext.xml中,配置平台事务管理器。而因为Dao层中所使用的技术不同,平台事务管理器也有所不同。如果Dao层技术使用的是MySQL或者mybatis,那么平台事务管理器应该是DataSourceTransActionManager,否则如果Dao层使用的技术是Hibenate,那么平台管理器应该是HibenateTransActionManager.因为这里我们Dao层中所使用的是mysql技术,所以平台事务管理器是DataSourceTransActionManager。
- 利用tx命名空间,从而声明切面类(封装增强的方法),并且设置事务的属性。(因为spring提供的事务控制)
- 利用aop命名空间,从而对对应的类进行增强。
所以对应的代码为:
业务代码:
public interface AccountDao {
void out(String name, int money);
void in(String name, int money);
}
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void out(String name, int money) {
String sql = "update account set balance = balance - ? where name = ?";
jdbcTemplate.update(sql,money,name);
}
@Override
public void in(String name, int money) {
String sql = "update account set balance = balance + ? where name = ?";
jdbcTemplate.update(sql,money,name);
}
}
public interface AccountService {
void transfer(String from, String to, int money);
}
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String from, String to, int money) {
accountDao.out(from,money);
int a = 1 / 0; //因为发生了报错,所以会回滚事务,否则,如果没有开启事务,那么from就会比原来少money
accountDao.in(to,money);
}
}
applicationContext.xml配置文件代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--组件扫描-->
<context:component-scan base-package="day7.demo"/>
<!--加载外部的jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="c3p0DataSource"/>
</bean>
<!--核心业务类添加到spring容器中-->
<bean id="accountdAO" class="day7.demo.dao.impl.AccountDaoImpl"></bean>
<bean id="accountService" class="day7.demo.service.impl.AccountServiceImpl"></bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="c3p0DataSource"></property>
</bean>
<!--声明切面类-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--
设置事务的属性:隔离级别 是否为只读 超时,默认是-1,表示不超时 广播
-->
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--抽取切点表达式-->
<aop:pointcut id="pointcut_id" expression="execution(* day7.demo.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut_id"></aop:advisor>
</aop:config>
</beans>
对应的测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Autowired
private AccountService accountService;
@Test
public void test(){
accountService.transfer("AA","BB",500);
}
}
在明白了xml来实现spring的事务控制之后,我们可以尝试使用注解开发来实现它的事务控制,对应的步骤为:
- 导入响应的依赖
- 创建业务代码,如上面的AccountDao,AccountDaoImpl,AccountService,AccountServiceImpl
- 利用注解@Repository,从而将dao中对应的类添加到spring容器中;利用@Service,从而将service层中对应的类添加到spring容器中
- 在配置文件applicationContext.xml中配置,进行组件扫描
- 在配置文件中我们同样需要配置数据源以及平台事务管理器
- 利用注解@Transactional,从而使得对修饰的类或者方法进行事务控制,对应的值可以是隔离级别等
- 此时完成了上面的步骤之后,还需要再配置文件中添加
<tx:annotation-driven/>
tx注解驱动
对应的代码为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--组件扫描-->
<context:component-scan base-package="day7.demo"/>
<!--加载外部的jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="c3p0DataSource"/>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="c3p0DataSource"></property>
</bean>
<!---
tx注解驱动,如果没有这一个,那么即使在业务类的上方使用了注解@Transactional进行事务控制
也还是没有开启事务
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
对应的业务类代码:
public interface AccountDao {
void out(String name, int money);
void in(String name, int money);
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void out(String name, int money) {
String sql = "update account set balance = balance - ? where name = ?";
jdbcTemplate.update(sql,money,name);
}
@Override
public void in(String name, int money) {
String sql = "update account set balance = balance + ? where name = ?";
jdbcTemplate.update(sql,money,name);
}
}
public interface AccountService {
void transfer(String from, String to, int money);
}
@Service("accountService")
@Transactional //设置事务控制,如果在类上面使用这个注解,将会对这个类的所有方法设置事务控制
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
//如果类也设置了事务控制,那么这个方法将会执行的是当前修饰这个
// 方法的@Transactional控制,即作用域小的那个注解的限制
@Transactional //设置事务控制
public void transfer(String from, String to, int money) {
accountDao.out(from,money);
int a = 1 / 0;
accountDao.in(to,money);
}
}
测试类代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Autowired
private AccountService accountService;
@Test
public void test(){
accountService.transfer("AA","BB",500);
}
}
值得注意的是,注解@Transactional既可以修饰类,也可以修饰方法,如果修饰方法,那么仅仅表示对当前这个方法设置了事务控制,而如果修饰的是类,那么表示对这个类的所有方法进行了事务控制。这时候如上面的AccountServiceImpl的情况一样,这个类被@Transactional注解修饰,并且存在一个方法被@Transactional修饰,那么这个方法将会被作用域小的@Transactional注解修饰。