Spring 事务管理(XML与注解)

一、事务简述

1、什么是事务

     事务(Transaction,简写为tx):在数据库中,事务是指一组逻辑操作,不论成功与失败都作为一个整体进行工作,要么全部执行成功,要么全部不执行(执行失败)。

2、事务必须满足ACID特性,缺一不可

1)原子性(Aromicity):事务是应用中不可再分的最小逻辑执行单位,要么都执行成功,要么都不执行。
2)一致性(Consistency):事务结束后,数据库中的数据是合法正确的状态(数据不被破坏),比如:A给B转账,无论转账成功与否转账之后A和B的账户总额和转账之前是相同的。
3)隔离性(Isolation):指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
        也可理解为并发执行的事务之间彼此相互独立、互不干扰。
4)持久性(Durability):事务一旦提交成功后,它对数据库的数据的改变是不可以的、不可回滚。

3、数据库并发问题:参考文章:数据库的并发问题

     并发访问会造成数据 脏读(dirty read)、不可重复读(unrepeatable read)、幻象读(phantom read)、第一类丢失更新、第二类丢失更新。    

4、事务的隔离级别

      为了解决并发访问带来的一些问题,就需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:

      由低到高分别为 Read uncommitted  > Read committed > Repeatable read > Serializable 。

1)Read uncommitted(读未提交)

      如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据

      解决了更新丢失,但还是可能会出现脏读

2)Read committed(读提交)

      如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

      解决了更新丢失和脏读问题

3)Repeatable read(可重复读取)

      可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。

      解决了更新丢失、脏读、不可重复读、但是还会出现幻读

4)Serializable(可序化)

      提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读

      解决了更新丢失、脏读、不可重复读、幻读(虚读)

 

      在MYSQL数据库中,支持上面四种隔离级别,默认的为Repeatable read(可重复读);

      在Oracle数据库中,只支持Serializeble(串行化)和Read committed(读已提交)这两种级别,默认为Read committed级别。

      隔离级别越高,数据库事务并发执行性能会越差,在项目中为了考虑并发性能一般使用 Read committed(读已提交),它能避免丢失更新和脏读,但是不可重复读和幻读不可避免。更多情况下使用悲观锁或乐观锁来解决。

5、事务传播行为

      所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

6、事务超时

      所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

7、事务类型

1)本地事务和分布式事务

     本地事务:就是普通事务,能保证单台数据库上操作的ACID,被限定在一台数据库上。

     分布式事务:涉及多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,是事务可以跨越多台数据库。

2)JDBC事务和JTA事务

     JDBC事务:就是数据库事务类型中的本地事务,通过 Connection对象的控制来管理事务。

     JTA事务:JTA(Java Transaction API)是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商提供实现,JTA事务比JDBC更强大,支持分布式事务。

3)通过编程实现事务可分为编程式事务和声明式事务

     编程式事务:通过编写代码来管理事务

     声明式事务:通过注解或XML配置来管理事务

 

二、Spring 事务管理

      Spring 支持编程式事务和声明式事务管理两种方式,这里学习声明式事务管理。

      Spring 声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

1、Spring 事务管理主要有3个接口:

1)TransactionDefinition接口:

     在Spring中,事务是通过TransactionDefinition接口来定义的,该接口包含与事务属性相关的方法,TransactionDefinition定义了五个表示隔离级别的常量,代表传播行为的常量,在TransactionDefinition中以int值表示超时时间。

2)Platform TransactionManager接口

     Platform TransactionManager.getInstance()方法返回一个Transaction Status对象,返回的Transaction Status对象可能代表一个新的或已经存在的事务(如果当前调用堆栈中有一个符合条件的事务)。

3)Transaction Status接口:封装了一些控制事务查询和执行的方法。

2、使用Spring管理事务时,首先需要告诉Spring使用哪一个事务管理器

    常用的事务管理器:

        DataSourceTransactionManager:支持JDBC和MyBatis

        HiberbateTransactionManager:支持Hibernate

3、Spring 事务回滚规则 

      指示Spring 事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

       默认配置下,spring只有在抛出的异常为运行时异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

三、基于 xml 配置声明式事务

引入依赖:

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.8.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.10</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

1、创建 model、dao和service

public class Card {
    private int id;
    private int money;
    ...
}

@Repository("cardDao")
public class CardDaoImpl implements ICardDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void transOut(int outId, int money) {
        jdbcTemplate.update("UPDATE card SET money = money - ? WHERE id = ?",
                money, outId);
    }

    public void transIn(int inId, int money) {
        jdbcTemplate.update("UPDATE card SET money = money + ? WHERE id = ?",
                money, inId);
    }
}

@Service("cardService")
public class CardServiceImpl implements ICardService {

    @Autowired
    private ICardDao cardDao;

    @Override
    public void trans(int outId, int inId, int money) {
        cardDao.transOut(outId, money);
        int a = 1 / 0;//模拟程序有异常
        cardDao.transIn(inId, money);
    }
}

2、applicationContext.xml 和 db.properties 配置文件

    db.properties 使用的Druid连接池

#key=value
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test_demo
jdbc.username=root
jdbc.password=123456
jdbc.initialSize=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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
		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">

    <!-- 从classpath的根路径去加载db.properties文件  NEVER:只查找properties-ref、location;-->
    <context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER" />

    <!-- 配置一个druid的连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="initialSize" value="${jdbc.initialSize}" />
    </bean>

    <!-- ID注释解析器 -->
    <context:annotation-config />
    <!-- IOC注解解析器 -->
    <context:component-scan base-package="cn.jq.springdemo"/>


    <!-- 1、配置事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 2、配置事务管理器增强 -->
    <tx:advice id="crudAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!-- service中的查询方法 -->
            <tx:method name="get*" read-only="true" propagation="REQUIRED" />
            <tx:method name="list*" read-only="true" propagation="REQUIRED" />
            <tx:method name="query*" read-only="true" propagation="REQUIRED" />
            <!-- service中其他方法(非查询) -->
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    <!-- 3、配置事务切面 -->
    <aop:config>
        <aop:pointcut expression="execution(* cn.jq.springdemo.service.*Service.*(..))" id="txPointcut" />
        <aop:advisor advice-ref="crudAdvice" pointcut-ref="txPointcut" />
    </aop:config>

    <!-- 配置spring的jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

3、测试,事务OK

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        ICardService cardService = context.getBean("cardService",ICardService.class);

        cardService.trans(1,2,1000);
    }
}

四、基于注解配置声明式事务

      基于注释配置事务:在Service中,使用 @Transactional注解

@Transactional 注解的属性 

属性类型描述
valueString可选的限定描述符,指定使用的事务管理器
propagationenum: Propagation可选的事务传播行为设置
isolationenum: Isolation可选的事务隔离级别设置
readOnlyboolean读写或只读事务,默认读写
timeoutint (in seconds granularity)事务超时时间设置
rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组
rollbackForClassName类名数组,必须继承自Throwable导致事务回滚的异常类名字数组
noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组
noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组

用法

       @Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

       虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

        默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

1、对service业务使用注解配置

import org.springframework.transaction.annotation.Transactional;

@Service("cardService")
@Transactional
public class CardServiceImpl implements ICardService {

    @Autowired
    private ICardDao cardDao;

    @Override
    public void trans(int outId, int inId, int money) {
        cardDao.transOut(outId, money);
        int a = 1 / 0;//模拟程序有异常
        cardDao.transIn(inId, money);
    }

    @Transactional(readOnly = true)
    public void listXxx() {
    }
}

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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
		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">

    <!-- 从classpath的根路径去加载db.properties文件  NEVER:只查找properties-ref、location;-->
    <context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER" />

    <!-- 配置一个druid的连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="initialSize" value="${jdbc.initialSize}" />
    </bean>

    <!-- ID注释解析器 -->
    <context:annotation-config />
    <!-- IOC注解解析器 -->
    <context:component-scan base-package="cn.jq.springdemo"/>

    <!-- 1、配置事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 2、事务注解解析器 -->
    <tx:annotation-driven transaction-manager="txManager"/>

    <!-- 配置spring的jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

3、测试 同上,结果OK

    

 

在Spring 中使用 事务管理还是很容易的,对于 AOP及事务的ACID等还是要加深理解。

更多可参看官方文档:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html

参考文章:Spring事务管理(详解+实例)

 

     站在前辈的肩膀上,每天进步一点点

ends~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值