Spring-02(动态代理,AOP,事物)

转账案例,使用事务控制,业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。 试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。


代码重复

使用动态代理解决问题:

动态代理:

特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
基于接口的动态代理
基于子类的动态代理

  • 基于接口的动态代理:
    涉及的类:Proxy
    提供者:JDK官方
    如何创建代理对象:
    使用Proxy类中的newProxyInstance方法
    创建代理对象的要求:
    被代理类最少实现一个接口,如果没有则不能使用
    newProxyInstance方法的参数:
    ClassLoader:类加载器
    它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
    Class[]:字节码数组
    它是用于让代理对象和被代理对象有相同方法。固定写法。
    InvocationHandler:用于提供增强的代码
    它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
    此接口的实现类都是谁用谁写。
基于接口的动态代理

        Producer producer = new Producer();
        IProducer proxyProducer = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy   代理对象的引用
                     * @param method  当前执行的方法
                     * @param args    当前执行方法所需的参数
                     * @return        和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
                        //1.获取方法执行的参数
                        Float money = (Float)args[0];
                        //2.判断当前方法是不是销售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;

                    }
                });
        proxyProducer.saleProduct(10000f);
  • 基于子类的动态代理:
    * 涉及的类:Enhancer
    * 提供者:第三方cglib库
    * 如何创建代理对象:
    * 使用Enhancer类中的create方法
    * 创建代理对象的要求:
    * 被代理类不能是最终类
    * create方法的参数:
    * Class:字节码
    * 它是用于指定被代理对象的字节码。
    *
    * Callback:用于提供增强的代码
    * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
    * 此接口的实现类都是谁用谁写。
    * 我们一般写的都是该接口的子接口实现类:MethodInterceptor
基于子类的动态代理

final Producer producer = new Producer();
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(),
                new MethodInterceptor() {
                    /**
                     * 执行北地阿里对象的任何方法都会经过该方法
                     * @param proxy
                     * @param method
                     * @param args
                     *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
                     * @param methodProxy :当前执行方法的代理对象
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
                        //1.获取方法执行的参数
                        Float money = (Float)objects[0];
                        //2.判断当前方法是不是销售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });

        cglibProducer.saleProduct(12000f);

Spring的AOP(面向切面编程)

AOP 相关术语
Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。 Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。

Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。

Target(目标对象): 代理的目标对象。

Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。

Aspect(切面): 是切入点和通知(引介)的结合。

AOP依赖

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
<!--spring基于XML的AOP配置步骤
    1、把通知bean也交给spring管理
    2、使用aop:config标签表明开始AOP的配置
    3、使用aop:aspect标签表明配置切面
           id:给切面提供唯一标识
           ref:指定通知类的bean的ID
     4、在aop:aspect标签的内部使用对应标签来配置通知的类型
           我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
           aop:before:表示配置前置通知
                method属性:用于指定Logger类中哪个方法是前置通知
                pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

         切入点表达式的写法:
            关键字:execution(表达式)
            表达式:
                访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
            标准的表达式写法:
                public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
            访问修饰符可以省略
                void com.itheima.service.impl.AccountServiceImpl.saveAccount()
            返回值可以使用通配符,表示任意返回值
                * com.itheima.service.impl.AccountServiceImpl.saveAccount()
            包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                * *.*.*.*.AccountServiceImpl.saveAccount())
            包名可以使用..表示当前包及其子包
                * *..AccountServiceImpl.saveAccount()
            类名和方法名都可以使用*来实现通配
                * *..*.*()
            参数列表:
                可以直接写数据类型:
                    基本类型直接写名称           int
                    引用类型写包名.类名的方式   java.lang.String
                可以使用通配符表示任意类型,但是必须有参数
                可以使用..表示有无参数均可,有参数可以是任意类型
            全通配写法:
                * *..*.*(..)

            实际开发中切入点表达式的通常写法:
                切到业务层实现类下的所有方法
                    * com.itheima.service.impl.*.*(..)
-->
spring基于XML的AOP配置

<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置spring的ioc,配置service对象-->
    <bean id="accountService" class="com.lmw.service.impl.AccountServiceImpl"></bean>

    <!--配置Logger类-->
    <bean id="logger" class="com.lmw.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知类型,建立通知和切入点方法的关联-->
            <!--标准的表达式写法-->
            <!--<aop:before method="printLog" pointcut="execution(public void com.lmw.service.impl.AccountServiceImpl.saveAccount())"></aop:before>-->
            <aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>

</beans>
通知的类型
    <!--配置AOP-->
    <aop:config>

        <!--配置切入点表达式
    配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
  此标签写在aop:aspect标签内部只能当前切面使用。
  它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
        <aop:pointcut id="pt1" expression="execution(* com.lmw.service.impl.*.*(..))"></aop:pointcut>

        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知类型,建立通知和切入点方法的关联-->
            <!--标准的表达式写法-->
            <!--<aop:before method="printLog" pointcut="execution(public void com.lmw.service.impl.AccountServiceImpl.saveAccount())"></aop:before>-->
            <!-- 配置前置通知:在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut="execution(* com.lmw.service.impl.*.*(..))"></aop:before>

            <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>

            <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

            <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>

<!--            &lt;!&ndash;配置切入点表达式
                配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
              此标签写在aop:aspect标签内部只能当前切面使用。
              它还可以写在aop:aspect外面,此时就变成了所有切面可用
            &ndash;&gt;
            <aop:pointcut id="pt1" expression="execution(* com.lmw.service.impl.*.*(..))"></aop:pointcut>-->
        </aop:aspect>
    </aop:config>

/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*-->

环绕通知方法写法

public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();//得到方法执行所需的参数
            System.out.println("前置环绕记录日志.......");
            rtValue = pjp.proceed();   //明确调用业务层方法
            System.out.println("后置环绕记录日志.......");
            return rtValue;
        } catch (Throwable throwable) {
            System.out.println("异常环绕记录日志.......");
            throwable.printStackTrace();
            throw new RuntimeException(throwable);

        }finally {
            System.out.println("最终环绕记录日志.......");
        }

    }

AOP基于注解配置

配置文件

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <context:component-scan base-package="com.lmw"></context:component-scan>

    <!--配置spring开启注解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

注解配置的后置通知和最终通知有顺序上的问题,一般使用环绕通知避免问题

@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 前置通知
     */
    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }
    /**
     * 异常通知
     */
    @AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }

/**
     *环绕通知
     */
    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

JdbcTemplate

它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多 的操作模板类。 操作关系型数据的: JdbcTemplate HibernateTemplate
操作 nosql 数据库的: RedisTemplate
操作消息队列的: JmsTemplate
我们今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的时候,除了要导入这个 jar 包 外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>

配置文件

    <bean id="accountDao" class="com.lmw.dao.impl.AccountDaoImpl">
        <!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/springtest"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/springtest");
        ds.setUsername("root");
        ds.setPassword("123456");


        JdbcTemplate jt = new JdbcTemplate();

        jt.setDataSource(ds);
        jt.execute("insert into account(name,money) values ('ccc',1000)");

        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

        JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);

        jt.execute("insert into account(name,money) values ('ccc',1000)");
//JdbcTemplate的CRUD操作
public class JdbcTemplateDemo3 {

    public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

        JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);

//        保存
        //jt.update("insert into account(name,money) values (?,?)", "eee", 3333f);
//        更新
        //jt.update("update account set name=?,money=? where id=?", "test", 1000f, 2);
//        删除
        //jt.update("delete from account where id = ?",8);
//        查询所有
       //List<Account> accounts = jt.query("select * from account where money>?",new AccountRowMapper(),1000f);
       List<Account> accounts = jt.query("select * from account where money>?",new BeanPropertyRowMapper<Account>(Account.class),1000f);

        for (Account account : accounts) {
            System.out.println(account);
        }
//        查询一个
        List<Account> account = jt.query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),1);
        System.out.println(account.isEmpty()?"没有内容":account.get(0));
//        返回一行一列
        Long count = jt.queryForObject("select count(*) from account where money>?",Long.class,1000f);
        System.out.println(count);

    }
}

class AccountRowMapper implements RowMapper<Account>{

    @Override
    public Account mapRow(ResultSet resultSet, int i) throws SQLException {
        Account account =new Account();
        account.setId(resultSet.getInt("id"));
        account.setName(resultSet.getString("name"));
        account.setMoney(resultSet.getFloat("money"));
        return account;
    }
}
持久层可以直接继承JdbcDaoSupport 通过getJdbcTemplate()获取其JdbcTemplate对象
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao

@Override
    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
    }

或者IOC的方式获取
private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

Spring事物

依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>

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:aop="http://www.springframework.org/schema/aop"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

   <bean id="accountService" class="com.lmw.service.impl.AccountServiceImpl">
       <property name="accountDao" ref="accountDao"></property>
   </bean>

   <bean id="accountDao" class="com.lmw.dao.impl.AccountDaoImpl">
       <!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
       <property name="dataSource" ref="dataSource"></property>
   </bean>



   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
       <property name="url" value="jdbc:mysql://localhost:3306/springtest"></property>
       <property name="username" value="root"></property>
       <property name="password" value="123456"></property>
   </bean>

   <!-- spring中基于XML的声明式事务控制配置步骤
      1、配置事务管理器
      2、配置事务的通知
              此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
              使用tx:advice标签配置事务通知
                  属性:
                      id:给事务通知起一个唯一标识
                      transaction-manager:给事务通知提供一个事务管理器引用
      3、配置AOP中的通用切入点表达式
      4、建立事务通知和切入点表达式的对应关系
      5、配置事务的属性
             是在事务的通知tx:advice标签的内部

   -->

   <!-- 配置事务管理器 -->
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="dataSource"></property>
   </bean>

   <!-- 配置事务的通知-->
   <tx:advice id="txAdvice" transaction-manager="transactionManager">
       <!-- 配置事务的属性
               isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
               propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
               read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
               timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
               rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
               no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
       -->
       <tx:attributes>
           <tx:method name="*" propagation="REQUIRED" read-only="false" />
           <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
       </tx:attributes>

   </tx:advice>

   <aop:config>
       <!-- 配置切入点表达式-->
       <aop:pointcut id="pt1" expression="execution(* com.lmw.service.impl.*.*(..))"></aop:pointcut>
       <!--建立切入点表达式和事务通知的对应关系 -->
       <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
   </aop:config>


</beans>

注解实现

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.lmw"></context:component-scan>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/springtest"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!-- spring中基于注解 的声明式事务控制配置步骤
        1、配置事务管理器
        2、开启spring对注解事务的支持
        3、在需要事务支持的地方使用@Transactional注解
     -->

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

    
</beans>
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;


    @Override
    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }


    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);
        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);

        accountDao.updateAccount(source);
            int i = 1/0;
        accountDao.updateAccount(target);

    }
}

纯注解实现
创建配置


配置

SpringConfiguration

@Configuration
@ComponentScan("com.lmw")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource(value = "jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}

JdbcConfig

public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate crateJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }


    @Bean(name = "dataSource")
    public  DataSource createDataSource(){
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

}

TransationConfig

    /*
    * 用于创建事务管理器对象
    * */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

测试类

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:bean.xml")
@ContextConfiguration(classes = SpringConfiguration.class)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350