基于xml的方式来实现事务的声明式声明

一.声明式事务

1.1 概述

注意:spring 声明式事务的底层就是AOP

1.2 配置ioc的标签

 

1.3 配置事务的流程

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

二.声明式事务的案例

2.1 案例背景描述:

数据表:

北京,上海的钱均为500 ,现在实现北京向上海进行转账50,正确的结果为北京450,上海550,
但是现在没有进行事务管理,在北京向上海进行转账,实现北京进行了减款操作,但是在上海进行加钱操作之前,出现了报错,后面代码无法执行,造成北京450,上海还是500
需要进行事务管理,出现异常进行回滚。

2.2 项目搭建

2.3 pom文件依赖的建立

 <!-- log4j的日志-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <!-- spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
   <!-- spring的事务管理-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
  <!-- mysql的驱动包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>

2.4 编写dao层

1.接口层

package com.ljf.spring.tx.xml.dao;

import com.ljf.spring.tx.xml.domain.Account;

public interface AccountDao {
    /**
     * 根据名称查询账户
     * @param accountName
     * @return
     */
    Account findAccountByName(String accountName);

    /**
     * 更新账户
     * @param account
     */
    void updateAccount(Account account);
}

2.实现层

package com.ljf.spring.tx.xml.dao.impl;

import com.ljf.spring.tx.xml.dao.AccountDao;
import com.ljf.spring.tx.xml.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.util.List;

/**
 * @ClassName: AccountDaoImpl
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2021/02/22 18:02:28 
 * @Version: V1.0
 **/
public class AccountDaoImpl  extends JdbcDaoSupport implements AccountDao {
    @Override
    public Account findAccountByName(String accountName) {
        List<Account> accounts = super.getJdbcTemplate().query("select id id ,account_name accountName,money money from tb_account where account_name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size()>1){
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }

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

2.5 编写service层

1.编写接口

package com.ljf.spring.tx.xml.service;

public interface AccountService {
    /**
     * 转账
     * @param sourceName    转成账户名称
     * @param targetName    转入账户名称
     * @param money         转账金额
     */
    public   void transferMoney(String sourceName, String targetName, Double money);
}

2.编写实现层

package com.ljf.spring.tx.xml.service.impl;

import com.ljf.spring.tx.xml.dao.AccountDao;
import com.ljf.spring.tx.xml.domain.Account;
import com.ljf.spring.tx.xml.service.AccountService;

/**
 * @ClassName: AccountServiceImpl
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2021/02/22 17:52:56 
 * @Version: V1.0
 **/
public class AccountServiceImpl implements AccountService {
    //spring的ioc 通过setter方法注入,属性名为actDao和spring-beans.xml中的标签名的id对应
    private AccountDao actDao;
    public AccountDao getActDao() {
        return actDao;
    }

    public void setActDao(AccountDao actDao) {
        this.actDao = actDao;
    }

    /**   北京,上海的钱均为500 ,现在实现北京向上海进行转账50,正确的结果为北京450,上海550,
     * 但是现在没有进行事务管理,在北京向上海进行转账,实现北京进行了减款操作,但是在上海进行加钱操作之前,出现了报错,后面代码无法执行,
     * 造成北京450,上海还是500
     * 转账
     * @param sourceName    转成账户名称
     * @param targetName    转入账户名称
     * @param money         转账金额
     */
    @Override
    public void transferMoney(String sourceName, String targetName, Double money) {
        System.out.println("正在实现转账功能 transfer....");
        //2.1根据名称查询转出账户
        Account source = actDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = actDao.findAccountByName(targetName);
        //2.3转出账户减钱
        System.out.println(":"+(source.getMoney()-money));
        source.setMoney(source.getMoney()-money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney()+money);
        //2.5更新转出账户
        actDao.updateAccount(source);
        int i=1/0;
        //2.6更新转入账户
        actDao.updateAccount(target);
    }
}

2.6 编写实体类

package com.ljf.spring.tx.xml.domain;

import java.io.Serializable;

/**
 * @ClassName: Account
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2021/02/19 23:21:27 
 * @Version: V1.0
 **/
public class Account implements Serializable {
    private Integer id;
    private String accountName;
    private Double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", accountName='" + accountName + '\'' +
                ", money=" + money +
                '}';
    }
}

2.7 编写resource下的文件

1.jdbc.properteis

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/nongda
jdbc.user=root
jdbc.password=

2.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: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:property-placeholder location="classpath:spring-jdbc.properties"></context:property-placeholder>
    <!-- 1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!-- 2.配置账户的持久层-->
    <bean id="accountDao" class="com.ljf.spring.tx.xml.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 3.配置业务层-->
    <bean id="acService" class="com.ljf.spring.tx.xml.service.impl.AccountServiceImpl">
        <property name="actDao" ref="accountDao"></property>
    </bean>

   </beans>


2.8 调用

1.调用类

package com.ljf.spring.tx.xml;

import com.ljf.spring.tx.xml.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
        //1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.根据id获取Bean对象
        AccountService as  = (AccountService)ac.getBean("acService");//获取配置文件的bean标签的id,
        as.transferMoney("北京","上海",50.0);
        System.out.println(as);

    }
}

2.执行结果

2.9 事务的配置

2.9.1 在pom添加依赖

    <!-- spring 切面包-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.7</version>
    </dependency>

2.9.2 配置文件配置事务

<?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:property-placeholder location="classpath:spring-jdbc.properties"></context:property-placeholder>
    <!-- 1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!-- 2.配置账户的持久层-->
    <bean id="accountDao" class="com.ljf.spring.tx.xml.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 3.配置业务层-->
    <bean id="acService" class="com.ljf.spring.tx.xml.service.impl.AccountServiceImpl">
        <property name="actDao" ref="accountDao"></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" rollback-for="*"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

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

    </aop:config>



</beans>

2.9.3 调用

package com.ljf.spring.tx.xml;

import com.ljf.spring.tx.xml.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
        //1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.根据id获取Bean对象
        AccountService as  = (AccountService)ac.getBean("acService");//获取配置文件的bean标签的id,
        as.transferMoney("北京","上海",50.0);
        System.out.println(as);

    }
}

报错了,刷新数据库,数据依然是500,进行了回滚

三.声明式事务不起作用的原因

 在工作中,看过别人写的代码出现了事务不回滚的现象。当然,事务不回滚的都是采用的声明式事务或者是注解事务;编程式事务都是自己写代码手动回滚的,因此是不会出现不回滚的现象。

    再说下 声明式 事务和注解事务回滚的原理:当被切面切中或者是加了注解的方法中抛出了RuntimeException异常时,Spring会进行事务回滚默认 情况下是 捕获到方法的 RuntimeException 异常 ,也就是说抛出只要属于运行时的异常(即 RuntimeException及其子类 )都能回滚但当抛出一个不属于运行时异常时,事务是不会回滚的。

    下面说说我经常见到的3种事务不回滚的产生原因:

(1)声明式事务配置切入点表达式写错了,没切中Service中的方法
(2)Service方法中,把异常给try catch了,但catch里面只是打印了异常信息,没有手动抛出RuntimeException异常
(3)Service方法中,抛出的异常不属于运行时异常(如IO异常),因为Spring默认情况下是捕获到运行时异常就回滚

3.1 在service的实现层加try  catch

package com.ljf.spring.tx.xml.service.impl;

import com.ljf.spring.tx.xml.dao.AccountDao;
import com.ljf.spring.tx.xml.domain.Account;
import com.ljf.spring.tx.xml.service.AccountService;

/**
 * @ClassName: AccountServiceImpl
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2021/02/22 17:52:56 
 * @Version: V1.0
 **/
public class AccountServiceImpl implements AccountService {
    //spring的ioc 通过setter方法注入,属性名为actDao和spring-beans.xml中的标签名的id对应
    private AccountDao actDao;
    public AccountDao getActDao() {
        return actDao;
    }

    public void setActDao(AccountDao actDao) {
        this.actDao = actDao;
    }

    /**   北京,上海的钱均为500 ,现在实现北京向上海进行转账50,正确的结果为北京450,上海550,
     * 但是现在没有进行事务管理,在北京向上海进行转账,实现北京进行了减款操作,但是在上海进行加钱操作之前,出现了报错,后面代码无法执行,
     * 造成北京450,上海还是500
     * 转账
     * @param sourceName    转成账户名称
     * @param targetName    转入账户名称
     * @param money         转账金额
     */
    @Override
    public void transferMoney(String sourceName, String targetName, Double money) {
        System.out.println("正在实现转账功能 transfer....");
        //2.1根据名称查询转出账户
        Account source = actDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = actDao.findAccountByName(targetName);
        //2.3转出账户减钱
        System.out.println(":"+(source.getMoney()-money));
        source.setMoney(source.getMoney()-money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney()+money);
        //2.5更新转出账户
        actDao.updateAccount(source);

       try{
           int i=1/0;
       }
       catch (Exception e){
           e.printStackTrace();

           throw new RuntimeException();
       }
        //2.6更新转入账户
        actDao.updateAccount(target);
    }
}

3.2 调用

没有回滚,北京进行了减50,上海没有加50

3.3 解决办法

在try catch块中抛出运行时异常

package com.ljf.spring.tx.xml.service.impl;

import com.ljf.spring.tx.xml.dao.AccountDao;
import com.ljf.spring.tx.xml.domain.Account;
import com.ljf.spring.tx.xml.service.AccountService;

/**
 * @ClassName: AccountServiceImpl
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2021/02/22 17:52:56 
 * @Version: V1.0
 **/
public class AccountServiceImpl implements AccountService {
    //spring的ioc 通过setter方法注入,属性名为actDao和spring-beans.xml中的标签名的id对应
    private AccountDao actDao;
    public AccountDao getActDao() {
        return actDao;
    }

    public void setActDao(AccountDao actDao) {
        this.actDao = actDao;
    }

    /**   北京,上海的钱均为500 ,现在实现北京向上海进行转账50,正确的结果为北京450,上海550,
     * 但是现在没有进行事务管理,在北京向上海进行转账,实现北京进行了减款操作,但是在上海进行加钱操作之前,出现了报错,后面代码无法执行,
     * 造成北京450,上海还是500
     * 转账
     * @param sourceName    转成账户名称
     * @param targetName    转入账户名称
     * @param money         转账金额
     */
    @Override
    public void transferMoney(String sourceName, String targetName, Double money) {
        System.out.println("正在实现转账功能 transfer....");
        //2.1根据名称查询转出账户
        Account source = actDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = actDao.findAccountByName(targetName);
        //2.3转出账户减钱
        System.out.println(":"+(source.getMoney()-money));
        source.setMoney(source.getMoney()-money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney()+money);
        //2.5更新转出账户
        actDao.updateAccount(source);

       try{
           int i=1/0;
       }
       catch (Exception e){
           e.printStackTrace();

           System.out.println("进行了事务的回滚");
           throw new RuntimeException();
       }
        //2.6更新转入账户
        actDao.updateAccount(target);
    }
}

3.4 再次运行

事务进行了回滚,ok,配置的声明式事务正确,实现了回滚操作

猜你喜欢

转载自blog.csdn.net/u011066470/article/details/113928348
今日推荐