(7)Spring——声明式事务--事务管理

1 事务概述

事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
事务的四个特性(ACID)

  1. 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
  2. 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
  3. 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
  4. 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

2 Spring的事务管理

1编程式事务管理

使用原生的JDBC API进行事务管理的步骤:

  1. 获取数据库连接Connection对象
  2. 取消事务的自动提交
  3. 执行操作
  4. 正常完成操作时手动提交事务
  5. 执行失败时回滚事务
  6. 关闭相关资源

评价

使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块 都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。

2 声明式事务管理

1.Spring既支持编程式事务管理,也支持声明式的事务管理。
2.大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
3.事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
4.Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制

3 Spring提供的事务管理器

Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager接口,它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
事务管理器的主要实现类:

  1. DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
  2. HibernateTransactionManager:用Hibernate框架存取数据库

3 案例实现

数据库表根据下面的图片就可以很方便的创建,这里就不写了!
在这里插入图片描述
在这里插入图片描述

1)创建接口

BookShopDao

public interface BookShopDao {
    //根据图书的书号获取图书的价格
    Double getBookPrice(String isbn);
    //根据图书的书号更新图书库存,一次只能买一本
    void updateBookStock(String isbn);
    //根据用户的id和图书的价格更新用户的余额
    void updateUserBalance(int userId , Double bookPrice);
}

BookShopService

public interface BookShopService {
    //购买图书
    void purchase(int userId , String isbn);
}

2)创建Spring的配置文件beans-tx.xml配置数据源和JdbcTemplate

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

    <!--配置自动扫描的包-->
    <context:component-scan base-package="com.spring.tx"></context:component-scan>

    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--配置数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--配置数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--开启事务注解支持
        当事务管理器的id是transactionManager时,可以省略指定transaction-manager属性
    -->
<!--    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>-->
    <tx:annotation-driven></tx:annotation-driven>
</beans>

上面xml文件中的外部文件见上篇博客druid.properties

3) 创建实现类

BookShopDaoImpl
package com.atguigu.spring.tx.dao.impl;

import com.atguigu.spring.tx.dao.BookShopDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Double getBookPrice(String isbn) {
        //写sql语句
        String sql = "select price from book where isbn = ?";
        //调用JdbcTemplate中的queryForObject方法
        Double price = jdbcTemplate.queryForObject(sql, Double.class,isbn);
        return price;
    }

    @Override
    public void updateBookStock(String isbn) {
        //写sql语句
        String sql = "update book set stock = stock - 1 where isbn = ?";
        //调用JdbcTemplate中的update方法
        jdbcTemplate.update(sql,isbn);
    }

    @Override
    public void updateUserBalance(int userId, Double bookPrice) {
        //写sql语句
        String sql = "update account set balance = balance - ? where id = ?";
        //调用JdbcTemplate中的update方法
        jdbcTemplate.update(sql,bookPrice,userId);
    }
}

BookShopServiceImpl

package com.atguigu.spring.tx.service.impl;

import com.atguigu.spring.tx.dao.BookShopDao;
import com.atguigu.spring.tx.service.BookShopService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;
    //在需要添加事务的方法上添加@Transactional注解
	@Transactional
    @Override
    public void purchase(int userId, String isbn) {
        //获取图书的价格
        Double bookPrice = bookShopDao.getBookPrice(isbn);
        //更新图书的库存
        bookShopDao.updateBookStock(isbn);
        //更新用户账户的余额
        bookShopDao.updateUserBalance(userId,bookPrice);
    }
}

4 事务的传播行为

1) 简介

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务传播属性可以在@Transactional注解的propagation属性中定义。

2) Spring的7种传播行为

1.REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行;否则就启动一个新的事务,并在自己的事务内运行。
2.REQUIRES_NEW 当前的方法必须启动新事务,并在自己的事务内运行;如果有事务正在运行,应该将它挂起。
3.SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中。
4.NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有运行的事务将它挂起
5.MANDATORY 当前的方法必须运行在事务中,如果没有正在运行的事务就抛出异常。
6.NEVER 当前的方法不应该运行在事务中,如果有正在运行的事务就抛出异常。
7.NESTED 如果有事务正在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行。

3)事务的隔离级别

1) 数据库事务并发问题

a) 脏读
①Transaction01将某条记录的AGE值从20修改为30。
②Transaction02读取了Transaction01更新后的值:30。
③Transaction01回滚,AGE值恢复到了20。
④Transaction02读取到的30就是一个无效的值。
简单来说就是你读到了别人更新但未提交的数据。
b) 不可重复度
①Transaction01读取了AGE值为20。
②Transaction02将AGE值修改为30并提交。
③Transaction01再次读取AGE值为30,和第一次读取不一致。
简单来说就是你两次读取的值不可能重复。
c) 幻读
①Transaction01读取了STUDENT表中的一部分数据。
②Transaction02向STUDENT表中插入了新的行。
③Transaction01读取了STUDENT表时,多出了一些行。
简单来说就是你两次读取的表中的记录不一样,好像出现幻觉似的。

2) 隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:
① 读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
② 读已提交:READ COMMITTED、
要求Transaction01只能读取Transaction02已提交的修改。
③ 可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
④ 串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
在这里插入图片描述

4) @Transactional注解中的其他属性

1) 触发事务回滚的异常

a) 默认情况下,捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。
b) @Transactional注解中设置回滚的属性
i. rollbackFor或rollbackForClassName属性:指定遇到时必须进行回滚的异常类型,可以为多个。
ii. noRollbackFor或noRollbackForClassName属性:指定遇到时不回滚的异常类型,可以为多个。
####2) 事务的超时时间
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
timeout超时事务属性可以设置事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。

3) 只读属性

如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
readOnly只读事务属性可以设置这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
【第八章】 对ORM的支持 之 8.1 概述 ——跟我学spring3 【第八章】 对ORM的支持 之 8.2 集成Hibernate3 ——跟我学spring3 【第八章】 对ORM的支持 之 8.3 集成iBATIS ——跟我学spring3 【第八章】 对ORM的支持 之 8.4 集成JPA ——跟我学spring3 【第九章】 Spring事务 之 9.1 数据库事务概述 ——跟我学spring3 【第九章】 Spring事务 之 9.2 事务管理器 ——跟我学spring3 【第九章】 Spring事务 之 9.3 编程式事务 ——跟我学spring3 【第九章】 Spring事务 之 9.4 声明式事务 ——跟我学spring3 【第十章】集成其它Web框架 之 10.1 概述 ——跟我学spring3 【第十章】集成其它Web框架 之 10.2 集成Struts1.x ——跟我学spring3 【第十章】集成其它Web框架 之 10.3 集成Struts2.x ——跟我学spring3 【第十章】集成其它Web框架 之 10.4 集成JSF ——跟我学spring3 【第十一章】 SSH集成开发积分商城 之 11.1 概述 ——跟我学spring3 【第十一章】 SSH集成开发积分商城 之 11.2 实现通用层 ——跟我学spring3 【第十一章】 SSH集成开发积分商城 之 11.3 实现积分商城层 ——跟我学spring3 【第十二章】零配置 之 12.1 概述 ——跟我学spring3 【第十二章】零配置 之 12.2 注解实现Bean依赖注入 ——跟我学spring3 【第十二章】零配置 之 12.3 注解实现Bean定义 ——跟我学spring3 【第十二章】零配置 之 12.4 基于Java类定义Bean配置元数据 ——跟我学spring3 【第十二章】零配置 之 12.5 综合示例-积分商城 ——跟我学spring3 【第十三章】 测试 之 13.1 概述 13.2 单元测试 ——跟我学spring3 【第十三章】 测试 之 13.3 集成测试 ——跟我学spring3

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_43555873

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值