Spring 框架(声明式)事务学习总结

 说明 :声明式事务部分:参考网上课程,自己写的总结,如有侵权,联系删除。

目录

Spring事务_事务简介

Spring事务_Spring事务管理方案

 Spring事务_Spring事务管理器

Spring事务_事务控制的API

PlatformTransactionManager接口

Spring事务_事务的相关配置

Spring事务_事务的传播行为 

Spring事务_事务的隔离级别

 Spring事务_注解配置声明式事务


Spring事务_事务简介

事务:不可分割的原子操作。即一系列的操作要么同时成功,要么 同时失败。

开发过程中,事务管理一般在service层,service层中可能会操作多次数据库,这些操作是不可分割的。否则当程序报错时,可能会造成数据异常。

如:张三给李四转账时,需要两次操作数据库:张三存款减少、李 四存款增加。如果这两次数据库操作间出现异常,则会造成数据错误。

  1. 准备数据库
    CREATE DATABASE `student` ;
    USE `spring`;
    DROP TABLE IF EXISTS `account`;
    CREATE TABLE `account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) DEFAULT NULL,
      `balance` double DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT
    CHARSET=utf8;
    insert  into `account`(`id`,`username`,`balance`) values (1,'张三',1000),(2,'李四',1000);
    
  2. 创建maven项目,引入依赖
        <dependencies>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.7</version>
            </dependency>
            <!--mysql驱动包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.27</version>
            </dependency>
            <!--druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.5</version>
            </dependency>
            <!--spring-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.3.21</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.3.13</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.3.13</version>
            </dependency>
            <!-- MyBatis与Spring的整合包,该包可以让Spring创建MyBatis的对象 -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.6</version>
            </dependency>
            <!-- junit,如果Spring5整合junit,则junit版本至少在4.12以上 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.3.13</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
  3. 创建配置文件
    <?xml version="1.0" encoding="UTF-8"?>
    <beans  xmlns="http://www.springframework.org/schema/beans"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            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">
    
        <!-- 包扫描 -->
        <context:component-scan base-package="com.dream"></context:component-scan>
        <!-- 读取配置文件 classpath:db.properties -->
        <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
        <!-- 创建druid数据源对象 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driverClassName}"></property>
            <property name="url" value="${jdbc.url}"></property>
            <property name="username" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
        </bean>
        <!-- Spring创建封装过的SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- Spring创建封装过的SqlSession -->
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
            <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
        </bean>
        <!-- MapperScannerConfigurer 该对象可以自动扫描持久层接口,并为接口创建代理对象 -->
        <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 配置扫描的接口包 -->
            <property name="basePackage" value="com.dream.dao"></property>
        </bean>
    </beans>
    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql:///student
    jdbc.username=root
    jdbc.password=
  4. 编写Java代码
    // 账户
    public class Account {
        private int id; // 账号
        private String username; // 用户名
        private double balance; // 余额
        
        // 省略getter/setter/tostring/构造方法
    }
    
    @Repository
    public interface AccountDao {
        // 根据id查找用户
        @Select("select * from account where id = #{id}")
        Account findById(int id);
        // 修改用户
        @Update("update account set balance =#{balance} where id = #{id}")
        void update(Account account);
    }
    
    @Service
    public class AccountService {
        @Autowired
        private AccountDao accountDao;
        /**
         * 转账
         * @param id1 转出人id
         * @param id2 转入人id
         * @param price 金额
         */
        public void transfer(int id1, int id2, double price) {
            // 转出人减少余额
            Account account1 =accountDao.findById(id1);
            account1.setBalance(account1.getBalance()-price);
            accountDao.update(account1);
            int i = 1/0; // 模拟程序出错
            // 转入人增加余额
            Account account2 = accountDao.findById(id2);
            account2.setBalance(account2.getBalance()+price);
            accountDao.update(account2);
       }
    }
  5. 测试
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:applicationContext.xml")
    public class AccountServiceTest {
        @Autowired
        private AccountService accountService;
        @Test
        public void testTransfer(){
            accountService.transfer(1,2,500);
       }
    }
    

    出错了把,第一个人的余额减少了,中途遇到除数为0的异常,结果呢后边不执行了。此时没有事务管理,所以事务处理位于业务层,即一个service方法是不能分割的,要么都成功,要么都失败,也就是事务回滚。

Spring事务_Spring事务管理方案

在service层手动添加事务可以解决该问题:

@Service(value = "accountServic2")
public class AccountServic2 {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private SqlSessionTemplate sqlSession;
    public void transfer(int id1, int id2, double price) {
        try{
            // account1修改余额
            Account account1 = accountDao.findById(id1);
            account1.setBalance(account1.getBalance()- price);
            accountDao.update(account1);
            int i = 1/0; // 模拟转账出错
            // account2修改余额
            Account account2 = accountDao.findById(id2);
            account2.setBalance(account2.getBalance()+price);
            accountDao.update(account2);
            sqlSession.commit();
        }catch(Exception ex){
            sqlSession.rollback();
        }
    }
}
java.lang.UnsupportedOperationException: Manual rollback is not allowed over a Spring managed SqlSession

但在Spring管理下不允许手动提交和回滚事务。此时我们需要使用 Spring的事务管理方案,在Spring框架中提供了两种事务管理方案: 

  1. 编程式事务:通过编写代码实现事务管理。
  2. 声明式事务:基于AOP技术实现事务管理。

在Spring框架中,编程式事务管理很少使用,我们对声明式事务管 理进行详细学习。

Spring的声明式事务管理在底层采用了AOP技术,其最大的优点在于无需通过编程的方式管理事务,只需要在配置文件中进行相关的 规则声明,就可以将事务规则应用到业务逻辑中。

使用AOP技术为service方法添加如下通知:

 Spring事务_Spring事务管理器

Spring依赖事务管理器进行事务管理,事务管理器即一个通知类,我们为该通知类设置切点为service层方法即可完成事务自动管理。 由于不同技术操作数据库,进行事务操作的方法不同。如:JDBC提 交事务是 connection.commit() ,MyBatis提交事务是 sqlSession.commit() ,

所以 Spring提供了多个事务管理器。

事务管理器名称作用
org.springframework.jdbc.datasource.DataSourceTransactionManager 针对JDBC技术提供的事务管理器。适用于JDBC和MyBatis。
org.springframework.orm.hibernate3.HibernateTransactionManager针对于Hibernate框架提供的事务管理器。适用于Hibernate框架。
org.springframework.orm.jpa.JpaTransactionManager针对于JPA技术提供的事务管理器。适用于JPA技术。
org.springframework.transaction.jta.JtaTransactionManager跨越了多个事务管理源。适用在两个或者是多个不同的数据源中实现事务控制。

我们使用MyBatis操作数据库,接下来使用 DataSourceTransactionManager 进行事务管理。

  1. 引入依赖
    <!-- 事务管理 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.13</version>
    </dependency>
    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
    
  2. 在配置文件中引入约束
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    
    
    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
  3. 进行事务配置
        <!-- 事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 进行事务相关配置 -->
        <tx:advice id = "txAdvice">
            <tx:attributes>
                <tx:method name="*"/>
            </tx:attributes>
        </tx:advice>
        <!-- 配置切面 -->
        <aop:config>
            <!-- 配置切点 -->
            <aop:pointcut id="pointcut"
                          expression="execution(* com.dream.service.*.*(..))"/>
            <!-- 配置通知 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
        </aop:config>
  4. 这就可以了

Spring事务_事务控制的API

Spring进行事务控制的功能是由三个接口提供的,这三个接口是 Spring实现的,在开发中我们很少使用到,只需要了解他们的作用即可:

PlatformTransactionManager接口

PlatformTransactionManager是Spring提供的事务管理器接口,所 有事务管理器都实现了该接口。该接口中提供了三个事务操作方 法:

  1. TransactionStatus getTransaction(TransactionDefinition definition):获取事务状态信息。
  2. void commit(TransactionStatus status):事务提交
  3. void rollback(TransactionStatus status):事务回滚

TransactionDefinition接口

TransactionDefinition是事务的定义信息对象,它有如下方法:

  1. String getName():获取事务对象名称。
  2. int getIsolationLevel():获取事务的隔离级别。
  3. int getPropagationBehavior():获取事务的传播行为。
  4. int getTimeout():获取事务的超时时间。
  5. boolean isReadOnly():获取事务是否只读。

TransactionStatus接口

TransactionStatus是事务的状态接口,它描述了某一时间点上事务的状态信息。它有如下方法:

  1. void flush() 刷新事务
  2. boolean hasSavepoint() 获取是否存在保存点
  3. boolean isCompleted() 获取事务是否完成
  4. boolean isNewTransaction() 获取是否是新事务
  5. boolean isRollbackOnly() 获取是否回滚
  6. void setRollbackOnly() 设置事务回滚

Spring事务_事务的相关配置

在 <tx:advice>中可以进行事务的相关配置:

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="*"/>
        <tx:method name="find*" read-only="true"/>
    </tx:attributes>
</tx:advice>

 <tx:method />中的属性:

  • name:指定配置的方法。 * 表示所有方法,
  • find* 表示所有以find开头的方法。
  • read-only:是否是只读事务,只读事务不存在数据的修改,数据库将会为只读事务提供一些 优化手段,会对性能有一定提升,建议在查询中开启只读事务。
  • timeout:指定超时时间,在限定的时间内不能完成所有操作就会抛异常。默认永不超时
  • rollback-for:指定某个异常事务回滚,其他异常不回滚。默认所有异常回滚。
  • no-rollback-for:指定某个异常不回滚,其他异常回滚。默认所有异常回滚。
  • propagation:事务的传播行为
  • isolation:事务的隔离级别

Spring事务_事务的传播行为 

事务传播行为是指多个含有事务的方法相互调用时,事务如何在这 些方法间传播。

如果在service层的方法中调用了其他的service方法,假设每次执行 service方法都要开启事务,此时就无法保证外层方法和内层方法处 于同一个事务当中。

// method1的所有方法在同一个事务中
public void method1(){
    // 此时会开启一个新事务,这就无法保证method1()
中所有的代码是在同一个事务中
    method2();
    System.out.println("method1");
}
public void method2(){
    System.out.println("method2");
}

事务的传播特性就是解决这个问题的,Spring帮助我们将外层方法 和内层方法放入同一事务中。

传播行为介绍
REQUIRED默认。支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的 选择。
SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED必须在事务状态下执行,如果没有事务则新建事务,如果当前有事务则创建 一个嵌套事务

Spring事务_事务的隔离级别

事务隔离级别反映事务提交并发访问时的处理态度,隔离级别越高,数据出问题的可能性越低,但效率也会越低。

隔离级别脏读不可重复读幻读
READ_UNCOMMITED(读取未提交内容YesYesYes
READ_COMMITED(读取提交内容NOYesYes
REPEATABLE_READ(重复读NONOYes
SERIALIZABLE(可串行化)NONONO

如果设置为DEFAULT会使用数据库的隔离级别。

SqlServer , Oracle默认的事务隔离级别是READ_COMMITED

Mysql的默认隔离级别是REPEATABLE_READ

 Spring事务_注解配置声明式事务

Spring事务_注解配置声明式事务

  1. 注册事务注解驱动
    <!-- 注册事务注解驱动 -->
    <tx:annotation-driven transaction-manager="transactionManager">
    </tx:annotation-driven>
  2. 在需要事务支持的方法或类上加@Transactional
    @Service
    // 作用于类上时,该类的所有public方法将都具有该
    类型的事务属性
    @Transactional(propagation = Propagation.REQUIRED,isolation =Isolation.DEFAULT)
    public class AccountService {
        @Autowired
        private AccountDao accountDao;
        /**
         * 转账
         * @param id1 转出人id
         * @param id2 转入人id
         * @param price 金额
         */
        // 作用于方法上时,该方法将都具有该类型的事务属性
        @Transactional(propagation = Propagation.REQUIRED,isolation =Isolation.DEFAULT)
        public void transfer(int id1, int id2,double price) {
            // account1修改余额
            Account account1 = accountDao.findById(id1);
            account1.setBalance(account1.getBalance()-price);
            accountDao.update(account1);
            int i = 1/0; // 模拟转账出错
            // account2修改余额
            Account account2 = accountDao.findById(id2);
            account2.setBalance(account2.getBalance()+price);
            accountDao.update(account2);
       }
    }
    

spring 学习结束!!!!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值