Spring学习_day7

AOP

Spring另一核心技术是AOP,所谓AOP就是面向切面编程,它能够在不修改源代码的基础上,对代码的功能做出扩展。所以它的优势是能够降低了代码的重复率,提高了开发效率,并且容易维护。

而AOP的底层是通过Spring 提供的动态代理实现的。在运行期间,Spring通过动态代理技术动态生成代理对象,代理对象方法执行时进行增强功能的介入,然后再去调用目标对象方法,从而完成功能的增强。

而实现动态代理,主要有2种技术:

  • JDK — 基于接口的动态代理,对应的处理步骤为:

    1. 创建目标类Target,以及目标接口TargetInterface
    2. 创建切面类Advice,来封装增强的方法
    3. 通过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 – 基于父类的动态代理,对应的步骤为:

    1. 导入相关的依赖spring-context到pom.xml,因为spring-context中含有spring-core,而core包下面含有cglib这个包。
      在这里插入图片描述

    2. 创建目标类Target

    3. 创建切面类Advice,封装增强的方法

    4. 创建增强器Enhancer对象

    5. 增强器调用setSuperClass(),来设置父类(目标对象)

    6. 增强器调用setCallBack()方法,设置回调,其中传递的参数为MethodInterceptor,并且重写这个参数的方法intercept,这样当代理对象调用对应的方法的时候,都会被这个方法拦截,从而实现方法的增强。

    7. 增强器调用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操作,对应的步骤为:

    1. 导入相关的依赖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>
      
    2. 创建目标类Target,目标接口TargetInterface

    3. 创建切面类MyAspect

    4. 将这些类添加到Spring容器中。即在applicationContext.xml中进行配置。

    5. 这时候经过了第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包下面的所有类的返回值为某一类型的所有方法。

      注意的是,如果包和类名之间使用..分隔,那么表示的是对应包以及子包下的某一个类的方法。

    6. 利用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,也即上面程序所写的.

    对应为什么优先考虑接口,可以参考以下文章:

    Java 中到底是应该用接口类型 还是实现类的类类型去引用对象?

    第52条:通过接口引用对象

  • 注解开发来实现AOP操作

    在明白通过xml来实现AOP操作之后,通过注解开发实现AOP操作就更加简单了,对应的代码为:

    1. 导入相关的依赖(和上面xml实现AOP操作的依赖是一样的)

    2. 创建目标类Target,目标接口TargetInterface

    3. 创建切面类MyAspect

    4. 利用@Component注解,从而将目标类Target,切面类MyAspect添加到Spring容器中。之所以使用的是@Component注解,是因为这些类并没有在controller层,也没有在dao层,service层,所以使用的是注解@Componenty,将其添加道spring容器中。

    5. 利用注解@Aspect,从而说明MyAspect是一个切面类

    6. 要说明切面类中的方法是什么类型的通知,那么就需要使用对应的注解即可,例如@Before说明这个增强的方法是一个前置方法,其余同理,所以对应的注解有5中:@Before(说明修饰的方法是前置通知的注解),@AfterReturning(后置通知),@AfterThrowing(异常通知),@After(最终通知,@Around(环绕通知)

      其中这个注解的值是一个切点表达式,用来说明这个增强的方法对应的是哪一些切点。

    7. 配置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的事务控制的基本步骤为:

  1. 导入相关的依赖
  2. 创建业务代码,这里我们以转账为例,所以需要创建AccounDao接口以及AccountDaoImpl,AccountService,AccountServiceImple类
  3. 在配置文件applicationContext.xml中,将上面的业务类添加到spring容器中
  4. 在配置文件applicationContext.xml中,配置平台事务管理器。而因为Dao层中所使用的技术不同,平台事务管理器也有所不同。如果Dao层技术使用的是MySQL或者mybatis,那么平台事务管理器应该是DataSourceTransActionManager,否则如果Dao层使用的技术是Hibenate,那么平台管理器应该是HibenateTransActionManager.因为这里我们Dao层中所使用的是mysql技术,所以平台事务管理器是DataSourceTransActionManager。
  5. 利用tx命名空间,从而声明切面类(封装增强的方法),并且设置事务的属性。(因为spring提供的事务控制)
  6. 利用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的事务控制之后,我们可以尝试使用注解开发来实现它的事务控制,对应的步骤为:

  1. 导入响应的依赖
  2. 创建业务代码,如上面的AccountDao,AccountDaoImpl,AccountService,AccountServiceImpl
  3. 利用注解@Repository,从而将dao中对应的类添加到spring容器中;利用@Service,从而将service层中对应的类添加到spring容器中
  4. 在配置文件applicationContext.xml中配置,进行组件扫描
  5. 在配置文件中我们同样需要配置数据源以及平台事务管理器
  6. 利用注解@Transactional,从而使得对修饰的类或者方法进行事务控制,对应的值可以是隔离级别等
  7. 此时完成了上面的步骤之后,还需要再配置文件中添加<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注解修饰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值