春季高效的交易管理

祝大家有美好的一天!

好吧,这个月的月底总是很紧张,只有第二天才开始“ Spring框架开发人员”课程的第二天,这是由同样美丽而生气的Yuri教授的精彩而有趣的课程(有些学生称他为需求水平)在DZ中),那么让我们看看我们为您准备的另一种材料。

走吧

引言

大多数时候,开发人员并不重视事务管理。 结果,要么大部分代码稍后都要重写,要么开发人员在不知道其实际工作方式或在具体情况下应使用哪些方面的情况下实施事务管理。

事务管理中的一个重要方面是确定事务的正确边界,何时开始事务,何时结束事务,何时将数据添加到数据库以及何时将其泵回(发生异常)。



对于开发人员来说,最重要的方面是了解如何在应用程序中最佳地实现事务管理。 因此,让我们看一下各种选项。

交易管理方法

可以通过以下方式管理事务:

1.通过编写自定义代码进行程序控制

这是一种旧的事务管理方法。

EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); EntityManager entityManager = entityManagerFactory.createEntityManager(); Transaction transaction = entityManager.getTransaction() try { transaction.begin(); someBusinessCode(); transaction.commit(); } catch(Exception ex) { transaction.rollback(); throw ex; } 

优点

  • 事务边界在代码中很明显。

缺点

  • 它是重复性的并且容易出错。
  • 任何错误都会产生很大的影响。
  • 您还需要编写许多模板,如果要从该方法调用另一个方法,则需要从代码中再次对其进行控制。

2.使用Spring进行事务管理

Spring支持两种类型的事务管理

1.软件交易管理 :您必须通过编程来管理交易。 此方法足够灵活,但难以维护。

2.声明式事务管理 :您将事务管理与业务逻辑分开。 您只能在基于XML的配置中使用注释进行事务管理。

我们强烈建议使用声明式事务。 如果您想知道原因,请继续阅读,否则,如果要实现此选项,请直接转至“声明性事务管理”部分。

现在,让我们详细了解每种方法。

2.1。 程序化交易管理:

Spring框架提供了两种用于程序化事务管理的工具。

一个 使用TransactionTemplate (Spring团队推荐):

让我们看看如何使用下面的示例代码来实现这种类型(摘自Spring文档,进行了一些更改)

请注意,代码段摘自Spring Docs。

上下文Xml文件:

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Definition for ServiceImpl bean --> <bean id="serviceImpl" class="com.service.ServiceImpl"> <constructor-arg ref="transactionManager"/> </bean> 

Service等级:

 public class ServiceImpl implements Service { private final TransactionTemplate transactionTemplate; //       PlatformTransactionManager public ServiceImpl(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } //       ,   ,    //       xml  this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); //30  ///    public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { //         public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); }} 

如果没有返回值,则将便捷的TransactionCallbackWithoutResult类与匿名类一起使用,如下所示:

 transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } }); 

  • TransactionTemplate类的实例是线程安全的,因此不支持所有对话框状态。
  • 尽管如此, TransactionTemplate实例仍支持配置状态,因此,如果一个类需要使用具有不同设置(例如,不同的隔离级别)的TransactionTemplate,则您需要创建两个不同的TransactionTemplate实例,尽管某些类可能使用相同的TransactionTemplate实例。

b。 直接使用PlatformTransactionManager实现:

让我们再次在代码中查看此选项。

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> public class ServiceImpl implements Service { private PlatformTransactionManager transactionManager; public void setTransactionManager( PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } DefaultTransactionDefinition def = new DefaultTransactionDefinition(); //     -  ,       def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { //   -  } catch (Exception ex) { txManager.rollback(status); throw ex; } txManager.commit(status); } 

现在,在继续进行下一种事务管理方法之前,让我们看一下如何决定选择哪种事务管理。

程序化声明式事务管理之间进行选择:

  • 仅当您执行少量交易操作时,程序交易管理才是一个不错的选择。 (在大多数情况下,这些不是事务。)
  • 只能在“程序事务管理”中显式设置事务名称。
  • 当您要显式控制事务管理时,应使用程序化事务管理。
  • 另一方面,如果您的应用程序包含大量事务操作,则值得使用声明式管理。
  • 声明式管理不允许您使用业务逻辑来管理事务,并且配置起来并不困难。

2.2。 声明式事务(通常在任何Web应用程序的几乎所有场景中使用)

步骤1 :在Spring应用程序的上下文xml文件中定义事务管理器。

 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/> <tx:annotation-driven transaction-manager="txManager"/> 

第2步 :通过在spring应用程序的上下文xml文件中添加条目来启用注释支持。

或将@EnableTransactionManagement添加到您的配置文件中,如下所示:

 @Configuration @EnableTransactionManagement public class AppConfig { ... } 

Spring建议使用@Transactional注释仅对特定类(和特定类的方法)进行注释,而@Transactional对接口进行注释。

这样做的原因是因为您将注释放在接口级别,并且如果您使用代理类( proxy-target-class = «true» )或交织的方面( mode = «aspectj» ),则代理基础结构无法识别事务参数和丛,例如交易行为将不适用。

步骤3 :将@Transactional批注添加到类(类方法)或接口(接口方法)。

 <tx:annotation-driven proxy-target-class="true"> 

默认配置: proxy-target-class="false"

  • 可以将@Transactional放在接口定义,接口方法,类定义或公共类方法之前。
  • 如果希望某些类方法(标有@Transactional批注)具有不同的属性设置,例如隔离级别或传播级别,则将批注放在方法级别以覆盖类级别的属性设置。
  • 在代理模式下(默认设置),只能拦截通过代理的“外部”方法调用。 这意味着“独立调用”,例如目标中调用目标的其他方法的方法,即使被调用的方法使用@Transactional标记,也不会在运行时导致实际事务。

现在让我们@Transactional批注@Transactional之间的区别

@Transactional (isolation=Isolation.READ_COMMITTED)

  • 默认值为Isolation.DEFAULT
  • 在大多数情况下,除非有特殊要求,否则将使用默认设置。
  • 告诉事务管理器( tx )当前的tx应该使用下一个隔离级别。 它必须安装在tx开始的位置,因为tx开始后我们无法更改隔离级别。

默认 :使用基本数据库中的默认隔离级别。

READ_COMMITTED (读取固定数据):一个常量,指示已防止脏读; 可能会出现非重复读取和幻像读取的情况。

READ_UNCOMMITTED (读取未提交的数据):此隔离级别表示一个事务可以读取尚未被其他事务删除的数据。

REPEATABLE_READ (读取重复性):一个常数,指示防止脏读和不可重复读; 可能会出现幻影读取。

SERIALIZABLE :永久,表示防止脏读,不可重复读和幻像读。

这些行话是什么意思:“脏”读,幻影读或重复读?

  • 脏读 :事务A写入。 同时,事务“ B”读取相同的记录,直到事务A完成为止,之后事务A决定回滚,现在我们对事务B进行了不兼容的更改。 这是肮脏的读物。 事务B在隔离级别READ_UNCOMMITTED下工作,因此它可以在事务完成之前读取事务A所做的更改。
  • 不可重复读取 :事务“ A”读取一些记录。 然后事务“ B”写入该记录并提交。 后来,事务A再次读取同一条记录,并且可能收到不同的值,因为事务B对此记录进行了更改并提交了它们。 这是非重复阅读。
  • 虚拟读取 :事务“ A”读取一系列记录。 同时,事务“ B”在与事务A相同的行中插入一条新记录,随后,事务A再次读取相同的范围,并且还接收到事务B刚刚插入的记录,这是一种幻象读取:事务多次检索了一系列记录从数据库中接收到不同的结果集(包含幻像记录)。

@Transactional(timeout=60)

默认值为基础事务系统的默认超时。

通知tx管理器等待直到tx空闲之前的时间长度,然后再决定是否回退无响应的事务。

@Transactional(propagation=Propagation.REQUIRED)

如果未指定,则默认传播行为为REQUIRED

其他选项是REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVERNESTED

需要

指示目标方法在没有活动tx的情况下无法工作。 如果在调用此方法之前tx已经在运行,则它将在同一tx中继续运行,否则新的tx将在调用此方法后不久开始。

REQUIRES_NEW

  • 指示每次调用目标方法时都应运行一个新的发送。 如果tx已经在运行,它将在开始新的操作之前暂停。

强制性

  • 指示目标方法需要激活的发送。 如果tx不继续,它将不会失败,并引发异常。

技术支持

  • 指示可以独立于tx执行目标方法。 如果发送成功,它将参与同一发送。 如果不使用tx执行,则在没有错误的情况下仍将执行。

  • 检索数据的方法是此选项的最佳选择。

NOT_SUPPORTED

  • 指示目标方法不需要事务上下文传播。
  • 基本上,在事务中执行但通过RAM执行操作的那些方法是此选项的最佳选择。

从不

  • 指示如果在事务处理中执行,则目标方法将引发异常。
  • 在大多数情况下,该选项在项目中不使用。

@Transactional (rollbackFor=Exception.class)

默认值: rollbackFor=RunTimeException.class

在Spring中,所有API类都会抛出RuntimeException,这意味着如果任何方法失败,则容器将始终回滚当前事务。

问题仅在于检查的异常。 因此,如果发生Checked Exception ,则此参数可用于声明性回滚事务。

@Transactional (noRollbackFor=IllegalStateException.class)

指示如果目标方法引发此异常,则不应发生回滚。

现在,事务管理的最后但最重要的步骤是发布@Transactiona l批注。 在大多数情况下,在应将注释放在何处会引起混淆:是在服务级别还是在DAO级别?

@Transactional :服务还是DAO级别?

服务是放置@Transactional的最佳位置,服务级别应在用户交互的详细级别上包含用例的行为,这在逻辑上涉及事务。

许多CRUD应用程序没有重要的业务逻辑,而这些业务逻辑的服务级别仅在控制器和数据访问对象之间传输数据,这是没有用的。 在这些情况下,我们可以将事务注释置于DAO级别。

因此,在实践中,您可以将它们放置在任何地方,取决于您自己。

另外,如果将@Transactional放在DAO层中,并且如果DAO层将由不同的服务重用,则将其放置在DAO层中将很困难,因为不同的服务可能有不同的要求。

如果您的服务级别使用Hibernate检索对象,并且假设您在域对象的定义中具有延迟初始化,那么您需要在服务级别打开事务,否则您将遇到ORM抛出的LazyInitializationException。

考虑另一个示例,您的服务级别可以调用两个不同的DAO方法来执行数据库操作。 如果您的第一个DAO操作失败,则其他两个操作可能会被转移,并且您将结束数据库的不一致状态。 服务级别注释可以使您免受此类情况的困扰。

希望本文对您有所帮助。

结束

看到您的评论或问题总是很有趣。

Source: https://habr.com/ru/post/zh-CN431508/


All Articles