• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

Spring 事务深入理解和使用

Spring winrains 来源:周鑫磊 6个月前 (03-31) 44次浏览

1 事务基本特性

1.1 原子性(Atomicity)

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

1.2 一致性(Consistency)

一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态

拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性

1.3 隔离性(Isolation)

隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离

即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

1.4 持久性(Durability)

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

2 事务控制分类

2.1 编程式事务控制

自己手动控制事务,就叫做编程式事务控制。

// 设置手动控制事务
Conn.setAutoCommite(false); 
// 开启一个事务
Session.beginTransaction();
  • 优点
    细粒度的事务控制: 可以对指定的方法、指定的方法的某几行添加事务控制,比较灵活
  • 缺点
    开发起来比较繁琐: 每次都要开启、提交、回滚

2.2 声明式事务控制

spring提供了对事务的管理, 这个就叫声明式事务管理

Spring提供了对事务控制的实现。用户如果想用Spring的声明式事务管理,只需要在配置文件中配置即可; 不想使用时直接移除配置。这个实现了对事务控制的最大程度的解耦,spring声明式事务管理,核心实现就是基于Aop

Spring声明式事务管理器类

  • Jdbc:DataSourceTransactionManager
  • Hibernate:HibernateTransactionManager

3 手写Spring事务框架

3.1 编程事务实现

  • 编程事务实现手动事务

使用编程事务实现,手动事务 begin、commit、rollback

@Component
public class TransactionUtils {
  @Autowired
  private DataSourceTransactionManager dataSourceTransactionManager;
  // 开启事务
  public TransactionStatus begin() {
    TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
    return transaction;
  }
  // 提交事务
  public void commit(TransactionStatus transactionStatus) {
    dataSourceTransactionManager.commit(transactionStatus);
  }
  // 回滚事务
  public void rollback(TransactionStatus transactionStatus) {
    dataSourceTransactionManager.rollback(transactionStatus);
  }
}
@Service
public class UserService {
  @Autowired
  private UserDao userDao;
  @Autowired
  private TransactionUtils transactionUtils;
  public void add() {
    TransactionStatus transactionStatus = null;
    try {
      transactionStatus = transactionUtils.begin();
      userDao.add("wangmazi", 27);
      int i = 1 / 0;
      System.out.println("我是add方法");
      userDao.add("zhangsan", 16);
      transactionUtils.commit(transactionStatus);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (transactionStatus != null) {
        transactionStatus.rollbackToSavepoint(transactionStatus);
      }
    }
  }
}
  • AOP技术封装手动事务
@Component
@Aspect
public class AopTransaction {
  @Autowired
  private TransactionUtils transactionUtils;
  // // 异常通知
  @AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))")
  public void afterThrowing() {
    System.out.println("程序已经回滚");
    // 获取程序当前事务 进行回滚
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  }
  // 环绕通知
  @Around("execution(* com.itmayiedu.service.UserService.add(..))")
  public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("开启事务");
    TransactionStatus begin = transactionUtils.begin();
    proceedingJoinPoint.proceed();
    transactionUtils.commit(begin);
    System.out.println("提交事务");
  }
}
  • 使用事务注意事项
    事务是程序运行如果没有错误,会自动提交事物,如果程序运行发生异常,则会自动回滚。 如果使用了try捕获异常时.一定要在catch里面手动回滚

事务手动回滚代码
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

3.2 声明事务实现

管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式

  • XML注解版本声明
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  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">
  <!-- 开启注解 -->
  <context:component-scan base-package="com.itmayiedu"></context:component-scan>
  <!-- 1. 数据源对象: C3P0连接池 -->
  <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
  </bean>
  <!-- 2. JdbcTemplate工具类实例 -->
  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
  </bean>
  <!-- 配置事物 -->
  <bean id="dataSourceTransactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
  </bean>
  <!-- 开启注解事物 -->
  <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
</beans>

使用

@Transactional
public void add() {
    userDao.add("wangmazi", 27);
    int i = 1 / 0;
    System.out.println("我是add方法");
    userDao.add("zhangsan", 16);
}

3.3 手写Spring注解版本事务

3.3.1 实现自定义注解

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:

  • @Target

说明了Annotation所修饰的对象范围

TYPE:类, 接口 (包括注释类型), 或 枚举 声明
FIELD:字段声明(包括枚举常量)
METHOD:方法声明(Method declaration)
PARAMETER:正式的参数声明
CONSTRUCTOR:构造函数声明
LOCAL_VARIABLE:局部变量声明
PACKAGE:包声明
TYPE_PARAMETER:类型参数声明
TYPE_USE:使用的类型
  • @Retention

表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

SOURCE:注释只在源代码级别保留,编译时被忽略
CLASS:注释将被编译器在类文件中记录,但在运行时不需要JVM保留。这是默认的行为
RUNTIME:注释将被编译器记录在类文件中,在运行时保留VM,因此可以反读
  • @Documented注解

表明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分

  • @Inherited:

是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的

3.3.2 自定义事务注解

//编程事务(需要手动begin 手动回滚  手都提交)
@Component()
@Scope("prototype") // 设置成原型解决线程安全
public class TransactionUtils {
  private TransactionStatus transactionStatus;
  // 获取事务源
  @Autowired
  private DataSourceTransactionManager dataSourceTransactionManager;
  // 开启事务
  public TransactionStatus begin() {
    transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
    return transactionStatus;
  }
  // 提交事务
  public void commit(TransactionStatus transaction) {
    dataSourceTransactionManager.commit(transaction);
  }
  // 回滚事务
  public void rollback() {
    System.out.println("rollback");
    dataSourceTransactionManager.rollback(transactionStatus);
  }
}
注解类
@Autowired
  private TransactionUtils transactionUtils;
  @AfterThrowing("execution(* com.itmayiedu.service.*.*.*(..))")
  public void afterThrowing() throws NoSuchMethodException, SecurityException {
    // isRollback(proceedingJoinPoint);
    System.out.println("程序发生异常");
    // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    // TransactionStatus currentTransactionStatus =
    // TransactionAspectSupport.currentTransactionStatus();
    // System.out.println("currentTransactionStatus:" +
    // currentTransactionStatus);
    transactionUtils.rollback();
  }
  // // 环绕通知 在方法之前和之后处理事情
  @Around("execution(* com.itmayiedu.service.*.*.*(..))")
  public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    // 调用方法之前执行
    TransactionStatus transactionStatus = begin(proceedingJoinPoint);
    proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出异常不会执行后面代码
    // 调用方法之后执行
    commit(transactionStatus);
  }
  public TransactionStatus begin(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException {
    // // 判断是否有自定义事务注解
    ExtTransaction declaredAnnotation = getExtTransaction(pjp);
    if (declaredAnnotation == null) {
      return null;
    }
    // 如果有自定义事务注解,开启事务
    System.out.println("开启事务");
    TransactionStatus transactionStatu = transactionUtils.begin();
    return transactionStatu;
  }
  public void commit(TransactionStatus transactionStatu) {
    if (transactionStatu != null) {
      // 提交事务
      System.out.println("提交事务");
      transactionUtils.commit(transactionStatu);
    }
  }
  public ExtTransaction getExtTransaction(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException {
    // 获取方法名称
    String methodName = pjp.getSignature().getName();
    // 获取目标对象
    Class<?> classTarget = pjp.getTarget().getClass();
    // 获取目标对象类型
    Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
    // 获取目标对象方法
    Method objMethod = classTarget.getMethod(methodName, par);
    // // 判断是否有自定义事务注解
    ExtTransaction declaredAnnotation = objMethod.getDeclaredAnnotation(ExtTransaction.class);
    if (declaredAnnotation == null) {
      System.out.println("您的方法上,没有加入注解!");
      return null;
    }
    return declaredAnnotation;
  }
  // 回滚事务
  public void isRollback(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException {
    // // 判断是否有自定义事务注解
    ExtTransaction declaredAnnotation = getExtTransaction(pjp);
    if (declaredAnnotation != null) {
      System.out.println("已经开始回滚事务");
      // 获取当前事务 直接回滚
      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      return;
    }
  }
使用自定义注解
@ExtTransaction
public void add() {
userDao.add("test001", 20);
int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21);
}

4 事务的传播行为和隔离级别

4.1 事物传播行为

  • @Transactional(propagation=Propagation.REQUIRED)
    如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
  • @Transactional(propagation=Propagation.NOT_SUPPORTED)
    容器不为这个方法开启事务
  • @Transactional(propagation=Propagation.REQUIRES_NEW)
    不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
  • @Transactional(propagation=Propagation.MANDATORY)
    必须在一个已有的事务中执行,否则抛出异常
  • @Transactional(propagation=Propagation.NEVER)
    必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
  • @Transactional(propagation=Propagation.SUPPORTS)
    如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

4.2 事务隔离级别

注解 描述
@Transactional(isolation = Isolation.READ_UNCOMMITTED) 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)) 读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE) 串行化

MYSQL: 默认为REPEATABLE_READ(可重复读)级别
SQLSERVER: 默认为READ_COMMITTED(读取已提交数据)

  • 脏读
    一个事务读取到另一事务未提交的更新数据
  • 不可重复读
    在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据. 相反, “可重复读”在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
  • 幻读
    一个事务读到另一个事务已提交的insert数据

作者:周鑫磊

来源:https://www.sparksys.top/archives/17


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (1)