10.Spring中事务控制

1.基于XML的AOP实现事务控制
2.基于注解的AOP实现事务控制

基于注解的AOP实现事务控制,方便演示,我们新建一个工程,复制上spring03_eesy_03account的代码,进行改造。
原来的bean.xml,beanFactory和代理Service这些配置可以不再需要,为类打上注解代替bean.xml的功能,然后就根据注解功能的标注逐一替代删除即可,为了展示有效代码,我删除了一些Dao和Service用不上的方法。

  • AccountServiceImpl
package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.util.TransationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    @Autowired
    private TransationManager transationManager;

    /**
     * 1.根据名称查询转出账户
     * 2.根据名称查询转入账户
     * 3.转出账户减钱
     * 4.转入账户加钱
     * 5.更新转出账户
     * 6.更新转入账户
     *
     * @param SourceName
     * @param targetName
     * @param moeny
     */
    @Override
    public void transfer(String SourceName, String targetName, Float moeny) {
        //2执行操作
        //2.1 据名称查询转出账户
        Account payUser = accountDao.findAccountbyname(SourceName);
        //2.2 根据名称查询转入账户
        Account recvUser = accountDao.findAccountbyname(targetName);
        //2.3 转出账户减钱
        payUser.setMoney(payUser.getMoney() - moeny);
        //2.4 转入账户加钱
        recvUser.setMoney(recvUser.getMoney() + moeny);
        //更新转出账户
        accountDao.updateAccount(payUser);
        int i = 1 / 0;
        //更新转入账户
        accountDao.updateAccount(recvUser);
    }

}
  • AccountDaoImpl
package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.util.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;
import java.util.List;

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private QueryRunner queryRunner;
    @Autowired
    private ConnectionUtils connectionUtils;

    public void updateAccount(Account account) {
        try {
            // queryRunner.update("update  account set name = ?,money = ? where id = ?",account.getId(),account.getName(),account.getMoney());
            queryRunner.update(connectionUtils.getConnection(), "update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 根据名称查找账户
     * 如果有唯一的一个结果就返回,如果没有就返回null
     * 如果结果集超过一个就抛异常
     *
     * @param
     * @return
     */
    @Override
    public Account findAccountbyname(String accountName) {
        try {
            List accountList = queryRunner.query(connectionUtils.getConnection(), "select * from account where name= ?", new BeanListHandler(Account.class), accountName);
            if (accountList == null || accountList.size() == 0) {
                return null;
            }
            if (accountList.size() > 1) {
                throw new RuntimeException("结果集不唯一");
            }
            return accountList.get(0);
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
}

  • ConnectionUtils
package com.itheima.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;

@Component("connectionUtils")
public class ConnectionUtils {
    private ThreadLocal tl = new ThreadLocal<>();
    @Autowired
    private DataSource dataSource;

    /**
     * 获取当前线程上的连接
     *
     * @return
     */
    public Connection getConnection() {
        try {
            //先从ThreadLocal上获取
            Connection connection = tl.get();
            //判断当前线程上是否有连接
            if (connection == null) {
                //从数据源中获取一个连接,并且存入ThreadLocal中
                connection = dataSource.getConnection();
                tl.set(connection);
            }
            //返回当前线程上的连接
            return connection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解绑
     */
    public void RemoveConnection() {
        tl.remove();
    }
}

  • TransationManager
package com.itheima.util;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;
@Component("transationManager")
@Aspect
public class TransationManager {
    @Autowired
    private ConnectionUtils connection;
    @Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
    public  void pt1(){

    }
    @Before("pt1()")
    //开启事务
    public void beginTransation() {
        try {
            connection.getConnection().setAutoCommit(false);
            System.out.println("---------前置通知---------");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @AfterReturning("pt1()")
    public void CommitTransation() {
        try {
            connection.getConnection().commit();
            System.out.println("---------后置通知---------");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @AfterThrowing("pt1()")
    //回滚事务
    public void RollBackTransation() {
        try {
            connection.getConnection().rollback();
            System.out.println("---------异常通知---------");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @After("pt1()")
    //释放连接
    public void releaseTransation() {
        try {
            connection.RemoveConnection();
            System.out.println("---------最终通知---------");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后xml的样子:




    
    
    
    
    
    
    
        
        
        
        
        
    

当我们配置完成进行Test的时候,我们会发现结果有异常。


10.Spring中事务控制_第1张图片
Can''t call rollback when autocommit=true

这是因为spring基于注解的Aop本来就是有问题的,这个上一章已经讲过,它的执行顺序是有问题的。
comit()或者rollback()的执行必须要由autocommit=true才可以,但因为执行顺序的混乱导致最终通知执行了,释放了连接,得到的连接自动提交就没有设置为false导致的。
要想使用注解解决这个问题,只能用环绕通知。

环绕通知

 @Around("pt1()")
    public Object aroundAdvice(ProceedingJoinPoint prj){
        try {
            Object rtValue = null;
            Object[] args = prj.getArgs();
            //开启事务
            this.beginTransation();
            //执行业务
            rtValue = prj.proceed(args);
            //提交事务
           this.CommitTransation();

            return rtValue;
        } catch (Throwable throwable) {
            //回滚事务
            this.RollBackTransation();
            throw new RuntimeException(throwable);
        }finally {
            //释放连接
            connection.RemoveConnection();
        }
    }

这是我写的环绕通知 没有像老师一样直接用this. 去调用,所以在catch那里有报错无法解决


10.Spring中事务控制_第2张图片
这是我写的环绕通知 没有像老师一样直接用this. 去调用,所以在catch那里有报错无法解决

接下来删掉TransationManager里的前置后置异常最终通知注解,让环绕通知运行就可以了。

4.spring中事务控制的一组API

我们还欠缺一些事情
只要是事务控制必然少不了提交和回滚,提交和回滚就是我们重复的代码,谁有提交和回滚,谁就是事务的通知。
sprin其实提供了事务管理器
拿基于xml的Aop配置举例,这些通知类型有两个是多余的,分别是前置通知和后置通知,因为一开始就可以在绑定线程的时候设置手动提交,最终通知可以在回滚和提交的时候直接释放。

10.Spring中事务控制_第3张图片
多余的

commit和rollback方法我们再来分析:commit和rollback的通知类型后置和异常能不能互换?当然是不能的,spring也知道这个,所以这些也是多余的。
Spring事务控制我们要明确的

  • 第一:JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方 案。
  • 第二:spring 框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在spring-tx-5.0.2.RELEASE.jar中。
  • 第三:spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我 们学习的重点是使用配置的方式实现。

Spring中事务控制的 API为我们提供了一个接口 :PlatformTransactionManager
PlatformTransationManager是个接口,我们在开发中都是使用它的实现类,真正管理事务的对象 :
org.springframework.jdbc.datasource.DataSourceTransactionManager -----使用 Spring JDBC 或 iBatis 进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager -----org.springframework.orm.hibernate5.HibernateTransactionManager 使用 Hibernate

5.spring事务控制的代码准备

Create一个新的工程,这个工程主要是环境的配置,将来会复用。
搭环境的时候我们需要的jar包有 spring-context spring-jdbc spring-tx 和数据库驱动 还需要一个aspectJwaver
spring04_eesy_01jdbctemplate 的代码拿过来复制进去 接口类我就不写在这了

AccountDaoImpl

package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.util.List;

/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

    @Override
    public Account findAccountById(Integer accountId) {
        List accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper(Account.class),accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }

    @Override
    public Account findAccountByName(String accountName) {
        List accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper(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 account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}

AccountServiceImpl

package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
public class AccountServiceImpl implements IAccountService{

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

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

    }



    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5更新转出账户
            accountDao.updateAccount(source);

       //     int i=1/0;

            //2.6更新转入账户
            accountDao.updateAccount(target);
    }
}

实体类 Account

package com.itheima.domain;

import java.io.Serializable;

/**
 * 账户的实体类
 */
public class Account implements Serializable {

    private Integer id;
    private String name;
    private Float money;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Float getMoney() {
        return money;
    }

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

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

bean.xml




    
    
        
    

    
    
        
    


    
    
        
        
        
        
    

6.spring基于XML的声明式事务控制-配置步骤

基于5.spring事务控制的代码准备,我们只需要在bean,xml中配置就好了。
步骤:配置事务管理器-----配置事务的通知----配置切入点表达式----建立切入点表达式与事务通知的关系---配置通知的属性
①配置事务管理器
这里需要注入一个数据,这个数据是我们的dataSource

(我总是记不住事务管理器要注入dataSource,导致我在重新配置代码的时候总是会报错:Caused by: java.lang.IllegalArgumentException: Property 'dataSource' is required)
为了加强记忆我点开源码看了一眼,的确需要一个dataSource的注入

10.Spring中事务控制_第4张图片
dataSource是事务管理器需要注入的属性

    
  
        
  

配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的



 配置事务的通知需要用到的标签叫做
  属性:
  id:给事务通知一个唯一的标识
  transaction-manager:给事务通知提供一个事务管理器引用

③配置AOP中的通用切入点表达式


        
  

④建立切入点表达式和事务通知的对应关系

 
  

⑤配置事务的属性

  • isolation:指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别。
  • propagation:指定事务的传播行为。默认值是REQUIRED,表示一定会有事务。增删改的选择。查询事务可以选择SUPPORTS。
  • read-only:用于指定事务只是只读,只有查询方法才能设置为ture,默认值是false,表示读写。
  • timeout:用于指定事务超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
  • rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚,没有默认值,表示任何异常都回滚。
  • no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚。

如果设定了rollback-for也设定了no-rollback-for 呢?无论什么情况,只要这两个值不指定,都是回滚的。


以后我们会有很多方法 这个时候简单点 可以用*通配符解决,像这样。


但是通配符 * 一下子就把所有的给代替了,查询方法怎么办呢,可以用find * ,但这样的前提是查询方法都以find...命名


这两个的谁的优先级更高呢?* 是全通配 find * 是部分通配,其实是find的优先级更高 但是需要注意 有些方法命名不规范的时候,find 就没有作用了。
完整bean.xml




    
        
    

    
    
        
    


    
    
        
        
        
        
    

    
    
        
    
    
    
        
        
        
            
            
        
    
    
    
        
        
        
    

此时基于测试代码去验证一下,当有异常的时候回滚,无异常的时候正常转账成功

package com.itheima.test;

import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private  IAccountService as;

    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);

    }
    @Test
    public  void testFind(){
        Account accountById = as.findAccountById(1);
        System.out.println(accountById);

    }
}

7.spring基于注解的声明式事务控制

写在前面,在基于注解时声明式事务控制的时候总是报错如下,检查代码后发现没有任何问题,新建工程好像无异常,再次运行后又报诡异的错误,接着我就基于xml的代码改造成基于注解的代码,仍然报错,我这才相信不是玄学,是代码有问题.
报错如下
java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.isCandidateClass(Ljava/lang/Class;Ljava/lang/Class;)Z
搜索后发现这是因为spring的jar包版本不一致导致的冲突,

10.Spring中事务控制_第5张图片
image.png

我把spring有关的jar包改成一致版本就解决此问题.

基于上个项目基于xml的声明式事务控制的代码,我们改造成基于注解的代码,首先按照基于注解的方式,原来xml中配置accountService和acountDao的配置就不要了


10.Spring中事务控制_第6张图片
acountDao

改造AccountDao,原来的AccountDao继承了JdbcTemplateSupport,我们已经知道了,在使用JdbcTemplateSupport的时候,是不能使用注解的,所以我们不再继承它,必须需要自己定义JdbcTemplate,那么这个JdbcTemplate要打上@Autowired标签,这个时候我们的ioc容器没有JdbcTemplate,需要在xml中配置.

    
    
        
    

然后改造AccountService


10.Spring中事务控制_第7张图片
AccountService

记下来记得要在bean.xml中配置创建Ioc容器时要扫描的包

 

原来xml基于事务控制的配置就可以不要了


10.Spring中事务控制_第8张图片
不要了

spring基于注解的声明式配置步骤:
以spring基于xml的声明式的代码为基础,进行spring基于注解的声明式配置,步骤简单来说是三步:配置事务管理器------开启spring对注解事务的支持-------在需要事务支持的地方打上@Transaction注解
①配置事务管理器

   
    
        
    

②开启spring对注解事务的支持

  

③在需要事务支持的地方打上@Transaction注解


10.Spring中事务控制_第9张图片
在AccountService上打上@Transaction注解

这样里面的方法就支持注解了,如果要加属性,可以直接加,但是这只是只读型的事务配置


10.Spring中事务控制_第10张图片
只读型事务配置

transfer作为读写型事务配置要另外配
10.Spring中事务控制_第11张图片
transfer作为读写型事务配置要另外配
8.spring基于纯注解的声明式事务控制

config包 SpringConfiguation类 JdbcConfig 连接数据库相关配置类 创建JdbcTemplate 创建数据源对象
TrancationConfig 和事务相关配置类
创建注解类,打上@Configuation标识为一个注解类.

1.创建数据源对象的时候不记得创建对象的类
2.创建TransactionConfig类时不记得该怎么做

你可能感兴趣的:(10.Spring中事务控制)