sping-tx

以xml配置形式
  • 导包,spring jar包下载地址

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
        </dependencies>
    
  • 目录结构
    在这里插入图片描述

  • 业务逻辑代码(以 xml 配置形式注入)
    service 调用 DAO层代码,dao 操作数据库。。。此处仅展示 dao 实现层代码

    public class AccountDaoImpl implements AccountDao {
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
        /**
         * 要么没有,要么只有唯一,根据Id查询
         **/
        @Override
        public Account findAccountById(int id) {
            List<Account> accountList = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<>(Account.class), id);
            return accountList.isEmpty()?null:accountList.get(0);
        }
        @Override
        public List<Account> findAllCount() {
            List<Account> list = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
            return list.isEmpty()?null:list;
        }
        @Override
        public Account findByName(Account account) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), account.getName());
            if (accounts.isEmpty())
                return null;
            if (accounts.size() > 0)
                throw new RuntimeException("结果集不唯一...");
            return accounts.get(0);
        }
    }
    
  • spring 配置文件

    <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"
           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">
    
        <bean id="accountDaoImpl" class="com.lhg.spring.dao.impl.AccountDaoImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate" />
        </bean>
        <bean id="accountServiceImpl" class="com.lhg.spring.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDaoImpl"/>
        </bean>
        <!--配置数据源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" />
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource" />
        </bean>
    </beans>
    
  • 测试代码

    public class Test {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
            List<Account> allCount = accountService.findAllCount();
            allCount.forEach(account -> System.out.println(account));
        }
    }
    
以纯注解形式
  • pom.xml ,新增测试 jar 包

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies>
    
  • 业务层数据访问层全部使用注解形式,并去掉 set 方法,其它代码不变
    在这里插入图片描述

  • 新建一个叫 config 的包,此包下新建 SpringConfig和JdbcConfig文件,分别用来代替 applicationContext.xml 和 JdbcTemplate 配置数据源部分

    /**
     * spring的配置类,相当于applicationContext.xml
     */
    @Configuration
    @ComponentScan("com.lhg.spring.*") //扫描包
    @Import(JdbcConfig.class)//引入其它的xml配置
    @PropertySource("db.properties")//读取数据库配置 properties文件注解
    public class SpringConfig {
    }
    
    
    /**
     * 和连接数据库相关的配置类
     */
    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;
        /**
         * 创建jdbctemplate对象,但此时没有进容器,要想进容器,需要一个bean注解
         */
        @Bean(name="jdbcTemplate")//是name不是value
        public JdbcTemplate createJdbcTemplate(DataSource dataSource){
            return new JdbcTemplate(dataSource);
        }
        @Bean(name="dataSource")
        public DataSource createDataSource(){
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName(driver);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    }
    
    
  • test 注解测试

    /**
     * 使用 Junit 单元测试,测试我们的配置
     * Srping 整合 junit 的配置
     * 1、导入 jar 包
     * 2、使用 Junit 提供的注解把 main 方法替换了,替换成 spring 提供的 @RunWith
     * 3、告知 spring 运行器,spring 和 IOC 创建时基于 xml 还是 注解,并且说明位置
     *  @ContextConfiguration
     *   locations:指定 xml 文件的位置,加上 classpath关键字表示在类路径下
     *   classes:指定注解类所在位置
     */
    @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java拥有IOC环境
    @ContextConfiguration(classes = SpringConfig.class)
    public class Test {
        @Autowired
        private AccountService accountService;
        @org.junit.Test
        public void test(){
            List<Account> allCount = accountService.findAllCount();
            allCount.forEach(account -> System.out.println("纯注解:"+account));
        }
    }
    
spring AOP(基于 xml 配置)

什么是 AOP:在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
说人话:简单的说就是把程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上对原有方法进行增强
优势:减少重复代码、提高开发效率、维护方便

  • AOP 实现步骤
    在这里插入图片描述

  • 导入 jar 包

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.13</version>
            </dependency>
        </dependencies>
    
  • 工程结构
    通用的 MVC 三层架构工程,为了简单,此工程并为用到 DAO 层代码,即没有查数据库

  • 需要增强的业务方法

    public class AccountServiceImpl implements AccountService {
        //对accountSave方法aop
        @Override
        public void accountSave() {
            System.out.println("accoutSave...");
            int i = 1 / 0;//有异常
        }
    }
    
  • 利用 LoggerUtil对原有方法增强

    public class LoggerUtil {
        public void beforeAdvice(){
            System.out.println("前置通知。。。开始打印日志....");
        }
        public void afterAdvice(){
            System.out.println("后置通知。。。打印完了日志....");
        }
        public void throwingrintAdvice(){
            System.out.println("异常通知。。打印了异常通知....");
        }
        public void finalAdvice(){
            System.out.println("最终通知打印了异常通知....");
        }
    }
    
  • applicationContext.xml (配置前置、后置、异常、最终四个通知)

    <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">
    
        <bean id="accountService" class="com.lhg.spring.service.impl.AccountServiceImpl" />
        <bean id="logger" class="com.lhg.spring.utils.LoggerUtil" />
        <aop:config>
            <!--配置切入点表达式,简化通知配置
            如果不写aop:pointcut 切入点表达式标签,在每个通知标签都要写 pointcut 属性,繁琐
            Id属性用于指定表达式的唯一标识,expression用于指定表达式内容
            此标签写在aop:aspect>标签内部只能当前切面使用
            还可以写在aop:aspect>外面,此时就变成所有切面可用
            -->
            <aop:pointcut id="mypoint" expression="execution(* com.lhg.spring.service.impl.*.*())"/>
            <aop:aspect id="logAdvice" ref="logger">
                <aop:before method="beforeAdvice" pointcut-ref="mypoint"/>
                <!--后置通知-->
                <aop:after-returning method="afterAdvice" pointcut-ref="mypoint"/>
                <!--异常通知-->
                <aop:after-throwing method="throwingrintAdvice" pointcut-ref="mypoint" />
                <!--最终通知-->
                <aop:after method="finalAdvice" pointcut-ref="mypoint"/>
    	        </aop:aspect>
        </aop:config>
    </beans>
    
  • test 进行测试

    public class AOPTest {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
            accountService.accountSave();
        }
    }
    

    效果如下
    在这里插入图片描述

  • 测试 spring 环绕通知

    • 在 LoggerUtil 新增环绕通知方法

      /**
           * spring 框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),
           * 此方法就相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数,在程序执行时,
           * spring 框架会提供该接口的实现类供我们使用
           **/
          public Object aroundPringLog(ProceedingJoinPoint pjp){
              try {
                  Object res ;
                  Object[] args = pjp.getArgs();//这个参数就是实际方法执行所需的参数
                  System.out.println("写在proceed之前是前置通知--->环绕..前置");
                  res = pjp.proceed(args);//明确调用业务层方法(切入点方法)
                  System.out.println("写在proceed之后是后置通知--->环绕..后置");
                  return res;
              }catch (Throwable t){//这个异常必须写Throwable,Exception拦不住的
                  System.out.println("写在proceed之后catch里面--->环绕..异常");
                  throw new RuntimeException(t);
              } finally {
                  System.out.println("写在proceed之后finally里面--->环绕..最终");
              }
          }
      
    • applicationContext.xml 新增环绕通知 AOP 配置

      <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">
          <bean id="accountService" class="com.lhg.spring.service.impl.AccountServiceImpl" />
          <bean id="logger" class="com.lhg.spring.utils.LoggerUtil" />
          <aop:config>
              <!--配置切入点表达式,简化通知配置
              Id属性用于指定表达式的唯一标识,expression用于指定表达式内容
              此标签写在aop:aspect>标签内部只能当前切面使用
              还可以写在aop:aspect>外面,此时就变成所有切面可用
              -->
              <aop:pointcut id="mypoint" expression="execution(* com.lhg.spring.service.impl.*.*())"/>
              <aop:aspect id="logAdvice" ref="logger">
                  <!--配置环绕通知-->
                  <aop:around method="aroundPringLog" pointcut-ref="mypoint" />
              </aop:aspect>
          </aop:config>
      </beans>
      
    • 测试效果
      在这里插入图片描述

spring AOP(基于 注解配置)
  • service 层改动

    @Service
    public class AccountServiceImpl implements AccountService {
        //对accountSave方法aop
        @Override
        public void accountSave() {
            System.out.println("accoutSave...");
            int i = 1 / 0;
        }
    }
    
  • LogggerUtils 改动(注意通知括号内必须带上"pointCut()")

    @Component
    @Aspect//表示当前类是一个切面
    public class LoggerUtil {
        //指定切入点表达式
        @Pointcut("execution(* com.lhg.spring.service.impl.*.*())")
        public void pointCut(){}
        @Before("pointCut()")
        public void beforeAdvice(){
            System.out.println("前置通知。。。开始打印日志....");
        }
        @AfterReturning("pointCut()")
        public void afterAdvice(){
            System.out.println("后置通知。。。打印完了日志....");
        }
        @AfterThrowing("pointCut()")
        public void throwingrintAdvice(){
            System.out.println("异常通知。。打印了异常通知....");
        }
        @After("pointCut()")
        public void finalAdvice(){
            System.out.println("最终通知打印了异常通知....");
        }
     }
    
  • applicationContext.xml 改动(注意新增context约束)

    <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">
        <!--配置 spring 创建容器要扫描的包-->
        <context:component-scan base-package="com.lhg.spring" />
        <!--配置 spring 开启AOP  注解支持-->
        <aop:aspectj-autoproxy />
    </beans>
    
  • 测试代码及效果

    public class AOPTest {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
            accountService.accountSave();
        }
    }
    

    在这里插入图片描述

    可以看到异常通知和最终通知输出结果并没有按预期那样的顺序,这个。。。是改变不了的。spring基于注解的 AOP 这四种通知确实有这种顺序的问题,所以在实际开发中要有个慎重的考虑,但是如果是基于注解的环绕通知是不会有这种顺序问题的,如下:

    @Component
    @Aspect//表示当前类是一个切面
    public class LoggerUtil {
        //指定切入点表达式
        @Pointcut("execution(* com.lhg.spring.service.impl.*.*())")
        public void pointCut(){}
        /**
         * spring 框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),
         * 此方法就相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数,在程序执行时,
         * spring 框架会提供该接口的实现类供我们使用
         **/
        @Around("pointCut()")
        public Object aroundPringLog(ProceedingJoinPoint pjp){
            try {
                Object res ;
                Object[] args = pjp.getArgs();//这个参数就是实际方法执行所需的参数
                System.out.println("写在proceed之前是前置通知--->环绕..前置");
                res = pjp.proceed(args);//明确调用业务层方法(切入点方法)
                System.out.println("写在proceed之后是后置通知--->环绕..后置");
                return res;
            }catch (Throwable t){//这个异常必须写Throwable,Exception拦不住的
                System.out.println("写在proceed之后catch里面--->环绕..异常");
                throw new RuntimeException(t);
            } finally {
                System.out.println("写在proceed之后finally里面--->环绕..最终");
            }
        }
    }
    

    测试效果如下
    在这里插入图片描述
    一个更加好理解的图示
    在这里插入图片描述

实战–基于 xml 的AOP 实现事务控制(spring内置声明式事务)

转账问题:如下,每次操作数据库时都获取一个连接,当出现异常时,前面正确执行的成功提交了,而异常后面的语句则没有提交,导致一方账户钱减少而另一方账户钱并没有增加。
解决办法:应该共用一个连接,用 ThreadLocal 把 Connection 和当前线程绑定,从而使一个线程中只有一个能控制事务的对象,事务控制应该都是放在业务层
在这里插入图片描述
在这里插入图片描述

  • 依赖 jar 包

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.lhg</groupId>
        <artifactId>spring_day03</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.13</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies>
    </project>
    
  • AccountDaoImpl 代码

    public class AccountDaoImpl  implements AccountDao {
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        /**
         * 要么没有,要么只有唯一,根据Id查询
         **/
        @Override
        public Account findAccountById(int id) {
            List<Account> accountList = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<>(Account.class), id);
            return accountList.isEmpty()?null:accountList.get(0);
        }
        @Override
        public List<Account> findAllCount() {
            List<Account> list = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
            return list.isEmpty()?null:list;
        }
    
        @Override
        public Account findByName(String name) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), name);
            if (accounts.isEmpty())
                return null;
            if (accounts.size() > 1)
                throw new RuntimeException("结果集不唯一...");
            return accounts.get(0);
        }
        @Override
        public int updateAccount(Account account) {
            return jdbcTemplate.update("update account set money=?,name=? where id=?",account.getMoney(),account.getName(),account.getId());
        }
    }
    
  • AccountServiceImpl 代码

    public class AccountServiceImpl implements AccountService {
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public Account findAccountById(int id) {
            return accountDao.findAccountById(id);
        }
    
        @Override
        public List<Account> findAllCount() {
            return accountDao.findAllCount();
        }
    
        @Override
        public int updateAccount(Account account) {
            return 0;
        }
    
        @Override
        public void transfer(String sourceName, String targetName, int money) {
            //根据名称查询转入转出账户
            Account source = accountDao.findByName(sourceName);
            Account target = accountDao.findByName(targetName);
            //转出账户减钱 转入账户加钱
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            //更新转入转出账户
            accountDao.updateAccount(source);
            int i = 1/0;
            accountDao.updateAccount(target);
        }
    }
    
  • applicationContext.xml 配置

    <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: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/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="accountDaoImpl" class="com.lhg.spring.dao.impl.AccountDaoImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate" />
        </bean>
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
            <property name="dataSource" ref="dataSource" />
        </bean>
        <bean id="accountServiceImpl" class="com.lhg.spring.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDaoImpl"/>
        </bean>
        <!--配置数据源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" />
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
        <!--配置事务管理器-->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
        <!--配置事务通知-->
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                <!--配置事务的属性:
                isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别
                propagation:用于指定事务的传播行为,默认值是REQUERD,表示一定会有事务,增删改的选择,查询方法可以使用SUPPORTS
                read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认值是false,表示读写
                rollback-for:用于指定一个异常,当该异常产生时,事务回滚,产生其它异常时事务不回滚,没有默认值,表示任何异常都回滚
                no-rollback-for:用于指定一个异常,当该异常产生时事务不回滚,产生其它异常时事务回滚,没有默认值,表示任何异常都回滚
                -->
                <tx:method name="*"  propagation="REQUIRED" read-only="false"/><!--通用匹配-->
                <tx:method name="find*" propagation="SUPPORTS" read-only="true" /><!--匹配以find开头的方法,优先级更高-->
            </tx:attributes>
        </tx:advice>
       <aop:config>
           <!--配置切入点表达式-->
          <aop:pointcut id="pt" expression="execution(* com.lhg.spring.service.impl.*.*(..))"/>
           <!--建立切入点表达式与事务通知的对应关系-->
           <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
       </aop:config>
    </beans>
    
  • 测试

    public class XMLTest {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountServiceImpl = (AccountService) ac.getBean("accountServiceImpl");
            accountServiceImpl.transfer("a","b",500);
        }
    }
    
  • 效果
    在这里插入图片描述

实战–基于注解的声明式事务控制
  • 导入相关依赖,测试 jar 包…略

  • service 层

    @Service
    @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只读型事务的配置,而我们的转账需要的是读写型事务的配置
    public class AccountServiceImpl implements AccountService {
        @Autowired
        private AccountDao accountDao;
    
        @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
        @Override
        public void transfer(String sourceName, String targetName, int money) {
            System.out.println("start transfer...");
            //根据名称查询转入转出账户
            Account source = accountDao.findByName(sourceName);
            Account target = accountDao.findByName(targetName);
            //转出账户减钱 转入账户加钱
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            //更新转入转出账户
            accountDao.updateAccount(source);
            int i = 1/0;
            accountDao.updateAccount(target);
        }
    }
    
  • dao 层

    @Repository
    public class AccountDaoImpl  implements AccountDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        @Override
        public Account findByName(String name) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), name);
            if (accounts.isEmpty())
                return null;
            if (accounts.size() > 1)
                throw new RuntimeException("结果集不唯一...");
            return accounts.get(0);
        }
    
        @Override
        public int updateAccount(Account account) {
            return jdbcTemplate.update("update account set money=?,name=? where id=?",account.getMoney(),account.getName(),account.getId());
        }
    
    }
    
  • applicationContext.xml

    <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: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/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">
    
        <context:component-scan base-package="com.lhg.spring" />
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
            <property name="dataSource" ref="dataSource" />
        </bean>
        <!--配置数据源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" />
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
        <!--配置事务管理器-->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
        <!--开启spring对注解事务的支持-->
       <tx:annotation-driven transaction-manager="txManager" />
    </beans>
    
  • 单元测试

    @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java拥有IOC环境
    //@ContextConfiguration(classes = SpringConfig.class)//纯注解
    @ContextConfiguration(locations = "classpath:applicationContext.xml")//注意写法
    public class Test {
        @Autowired
        private AccountService accountServiceImpl;
        @org.junit.Test
        public void test(){
            accountServiceImpl.transfer("a","b",500);
        }
    }
    
  • 效果:转账出现异常时,事务成功回滚

实战–基于纯注解的声明式事务控制
  • service 层和 dao 层 不变,使用注解注入

  • 新建一个 config 的包,在里面分别建三个 java 文件

    • SpringConfig.java

      /**
       * spring的配置类,相当于applicationContext.xml
       */
      @Configuration
      @ComponentScan("com.lhg.spring.*")
      @Import({JdbcConfig.class, TransactionConfig.class})//引入其它的配置
      @PropertySource("db.properties")//读取数据库配置文件注解
      @EnableTransactionManagement//开启对注解的支持,替换xml中<tx:annotation-driven transaction-manager="txManager" />
      public class SpringConfig {
      }
      
    • JdbcConfig.java

      /**
       * 和连接数据库相关的配置类
       */
      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;
          /**
           * 创建jdbctemplate对象,但此时没有进容器,要想进容器,需要一个bean注解
           */
          @Bean(name="jdbcTemplate")//是name不是value
          public JdbcTemplate createJdbcTemplate(DataSource dataSource){
              return new JdbcTemplate(dataSource);
          }
          @Bean(name="dataSource")
          public DataSource createDataSource(){
              DriverManagerDataSource dataSource = new DriverManagerDataSource();
              dataSource.setDriverClassName(driver);
              dataSource.setUrl(url);
              dataSource.setUsername(username);
              dataSource.setPassword(password);
              return dataSource;
          }
      }
      
      
    • TransactionConfig.java

      /**
       * 和事务相关的配置类,替换xml中
       *     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       *         <property name="dataSource" ref="dataSource" />
       *     </bean>
       */
      public class TransactionConfig {
          @Bean(name="transactionManager")
          public PlatformTransactionManager createTransactionManager(DataSource dataSource){
              return new DataSourceTransactionManager(dataSource);
          }
      }
      
  • 测试结果:有异常时事务回滚,没异常时转账成功

    /**
     * 使用 Junit 单元测试,测试我们的配置
     * Srping 整合 junit 的配置
     * 1、导入 jar 包
     * 2、使用 Junit 提供的注解把 main 方法替换了,替换成 spring 提供的 @RunWith
     * 3、告知 spring 运行器,spring 和 IOC 创建时基于 xml 还是 注解,并且说明位置
     *  @ContextConfiguration
     *   locations:指定 xml 文件的位置,加上 classpath关键字表示在类路径下
     *   classes:指定注解类所在位置
     */
    @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java拥有IOC环境
    @ContextConfiguration(classes = SpringConfig.class)//纯注解
    //@ContextConfiguration(locations = "classpath:applicationContext.xml")
    public class Test {
        @Autowired
        private AccountService accountServiceImpl;
        @org.junit.Test
        public void test(){
            accountServiceImpl.transfer("a","b",500);
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值