Spring入门笔记--事务

事务

概念

定义:数据库事务是构成单一逻辑工作单元的操作集合
数据库事务概念
ACID特性:

  1. 原子性(Atomicity):事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。
  2. 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。一致性状态是指:
    1. 系统的状态满足数据的完整性约束(主码,参照完整性,check约束等)
    2. 系统的状态反应数据库本应描述的现实世界的真实状态,比如转账前后两个账户的金额总和应该保持不变。
  3. 隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
  4. 持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。

在事务的ACID特性中,C即一致性是事务的根本追求,而对数据一致性的破坏主要来自两个方面

  • 事务的并发执行
  • 事务故障或系统故障

并发控制技术保证了事务的隔离性,使数据库的一致性状态不会因为并发执行的操作被破坏。
日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的对数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。

常见并发异常:

脏读、脏写:事务1的回滚导致事物2的读、写失效。数据未提交成功。
丢失更新:事务1和事务2的操作相抵消。
不可重复读:对于确定的某一行数据。由于事务1的提交,事务2前后两次读操作结果不一样。
幻读:对某个范围不确定多行数据查询,前后两次结果不一样。

所有事务隔离级别都不允许出现脏写,InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生
InnoDB有三种行锁的算法:

  1. Record Lock:单个行记录上的锁。
  2. Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
  3. Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

Spring事务:

事务属性的种类: 传播行为、隔离级别、只读和事务超时

  1. 传播行为定义了被调用方法的事务边界。解决业务方法之间调用事务统一性问题。
传播行为 意义
PROPERGATION_MANDATORY 表示方法必须运行在一个事务中,如果当前事务不存在,就抛出异常
PROPAGATION_NESTED 表示如果当前事务存在,则方法应该运行在一个嵌套事务中。否则,它看起来和 PROPAGATION_REQUIRED 看起来没什么俩样。支持当前事务,新增Savepoint点,与当前事务同步提交或回滚
PROPAGATION_NEVER 表示方法不能运行在一个事务中,否则抛出异常
PROPAGATION_NOT_SUPPORTED 表示方法不能运行在一个事务中,如果当前存在一个事务,则该方法将被挂起
PROPAGATION_REQUIRED 表示当前方法必须运行在一个事务中,如果当前存在一个事务,那么该方法运行在这个事务中,否则,将创建一个新的事务
PROPAGATION_REQUIRES_NEW 表示当前方法必须运行在自己的事务中,如果当前存在一个事务,那么这个事务将在该方法运行期间被挂起
PROPAGATION_SUPPORTS 表示当前方法不需要运行在一个是事务中,但如果有一个事务已经存在,该方法运行在这个事务中;没有则以非事务方式运行
  1. 隔离级别

在操作数据时可能带来 3 个副作用,分别是脏读、不可重复读、幻读。为了避免这 3 中副作用的发生,在标准的 SQL 语句中定义了 4 种隔离级别,分别是未提交读、已提交读、可重复读、可序列化。而在 spring 事务中提供了 5 种隔离级别来对应在 SQL 中定义的 4 种隔离级别,如下:

隔离级别 意义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 允许读取未提交的数据(对应未提交读),可能导致脏读、不可重复读、幻读
ISOLATION_READ_COMMITTED 允许在一个事务中读取另一个已经提交的事务中的数据(对应已提交读)。可以避免脏读,但是无法避免不可重复读和幻读
ISOLATION_REPEATABLE_READ 一个事务不可能更新由另一个事务修改但尚未提交(回滚)的数据(对应可重复读)。可以避免脏读和不可重复读,但无法避免幻读
ISOLATION_SERIALIZABLE 这种隔离级别是所有的事务都在一个执行队列中,依次顺序执行,而不是并行(对应可序列化)。可以避免脏读、不可重复读、幻读。但是这种隔离级别效率很低,因此,除非必须,否则不建议使用。
  1. 只读
    如果在一个事务中所有关于数据库的操作都是只读的,也就是说,这些操作只读取数据库中的数据,而并不更新数据,那么应将事务设为只读模式( READ_ONLY_MARKER ) , 这样更有利于数据库进行优化 。
    因为只读的优化措施是事务启动后由数据库实施的,因此,只有将那些具有可能启动新事务的传播行为 (PROPAGATION_NESTED 、 PROPAGATION_REQUIRED 、 PROPAGATION_REQUIRED_NEW) 的方法的事务标记成只读才有意义。
    如果使用 Hibernate 作为持久化机制,那么将事务标记为只读后,会将 Hibernate 的 flush 模式设置为 FULSH_NEVER, 以告诉 Hibernate 避免和数据库之间进行不必要的同步,并将所有更新延迟到事务结束。

  2. 事务超时
    如果一个事务长时间运行,这时为了尽量避免浪费系统资源,应为这个事务设置一个有效时间,使其等待数秒后自动回滚。与设置“只读”属性一样,事务有效属性也需要给那些具有可能启动新事物的传播行为的方法的事务标记成只读才有意义。

编程式事务控制对象

PlatformTransactionManager: 平台事务管理接口,如果是JDBC和Mybatis,实现是DataSourceTransactionManager
TransactionDefinition:封装事务的参数
TransactionStatus: 事务状态信息,比如保存点、是否提交

基于XML的声明式事务控制

Spring声明式事务控制底层就是AOP
AccountServiceImpl.java

@Service("accountService")
public class AccountServiceImpl implements AccountService{
    @Autowired
    private AccountDao accountDao;
    @Override
    public void transfer(String from, String to, double amount) {
        accountDao.out(from, amount);
        // 抛出异常,用来测试事务是否生效
        System.out.println(1/0);
        accountDao.in(to, amount);
    }
}

transactionTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class transactionTest {
    @Autowired
    private AccountService accountService;
    @Test
    public void test() {
        accountService.transfer("Jane", "TOM", 50);
    }
}

applicationContext.xml

<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="databaseProp"/>
</bean>
<!--通知 事物的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--事务的属性信息-->
        <tx:attributes>
                <tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
        </tx:attributes>
</tx:advice>
<!--配置事物aop的织入-->
<aop:config>
        <aop:advisor advice-ref="txAdvice"
                     pointcut="execution(* com.example.yihao_spring_aop.transaction.service.*.*(..))"/>
</aop:config>

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

applicationContext.xml

  <tx:annotation-driven transaction-manager="transactionManager"/>
  <!--配置平台事务管理器-->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <!--配置对应的数据库连接信息-->
          <property name="dataSource" ref="databaseProp"/>
  </bean>

transactionTest.java

@Service("accountService")
@Transactional(isolation = Isolation.REPEATABLE_READ)
public class AccountServiceImpl implements AccountService{
    @Autowired
    private AccountDao accountDao;
    @Override
    @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED)
    public void transfer(String from, String to, double amount) {
        accountDao.out(from, amount);
        // 抛出异常,用来测试事务是否生效
        System.out.println(1/0);
        accountDao.in(to, amount);
    }
}
posted @   Saski&Naruto  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示