专栏首页眯眯眼猫头鹰的小树杈猫头鹰的深夜翻译:spring事务管理

猫头鹰的深夜翻译:spring事务管理

简介

  • 大多数时候,开发者极少关注事务管理从而导致大量代码需要重新开发,或是实现事务的时候没有注意事务究竟是如何实现的以及在这些场景中需要关注的维度。
  • 事务管理的一个重要方面是定义正确的事务边界,例如事务何时开始,什么时候应该结束,什么时候应该在数据库中提交数据,什么时候应该回滚(在出现异常的时候)。
  • 对于开发人员而言,最重要的是了解如何在应用程序中更好的实现事务管理。所以现在让我们用不同的方式探索事务。

管理事务的方法

事务可以用以下方式管理:

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的配置来管理事务。

强烈建议使用声明式事务。如果想知道其原因,请阅读下面的内容,否则,可以直接跳转到声明式事务管理实现的部分。

现在,让我们细致的分析每一种事务管理方法。

编程式事务管理

Spring Framework提供了两种编程式事务管理方法。 a. 使用TransactionTemplate (Spring推荐这种实现): Context Xml file:

<!-- 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;
  // 使用构造器注入来使用PlatfromTransactionManager  
  public ServiceImpl(PlatformTransactionManager transactionManager)
  {     
       this.transactionTemplate = new TransactionTemplate(transactionManager);   
  }
  
  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(比如不同的隔离级别),那么就需要配置两个不同的实例。

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();
    // explicitly setting the transaction name is something that can only be done programmatically
    def.setName("SomeTxName");
 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus status = txManager.getTransaction(def);
    try 
    {    
    // execute your business logic here
    }
    catch (Exception ex)
    {    
        txManager.rollback(status);   
    throw ex;
    }
    txManager.commit(status);
}

在进入声明式事务管理之前,让我们看看如何选择事务管理方式:

  • 只有在少量事务操作时,编程式事务管理更佳合适。
  • 只能通过编程式事务管理设置事务的名称
  • 当希望显示管理事务时,应当使用编程式事务管理
  • 另一方面,如果您的应用程序具有大量事务操作,则声明式事务管理是值得的。
  • 声明式事务管理使事务代码也业务代码分离,并且配置难度不大。

声明式事务管理(几乎用于所有web应用场景)

第一步:在spring应用程序上下文xml文件中定义事务管理器。

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>

第二步:通过在spring应用程序上下文XML文件中添加以下条目,打开对事务注释的支持。

<tx:annotation-driven transaction-manager="txManager"/>

或是在配置类中添加@EnableTransactionManagement

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

Spring建议只使用@Transactional来注解具体类(以及具体类的方法),而不是接口。

原因是如果在接口上注解,并且使用基于类的代理(proxy-target-class="true")或是aop(mode="aspectj"),那么事务注解将无法被识别。

第三步:将注解添加在类(或是类的方法)或是接口(或是接口的方法上)

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

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

  • @Transactional注解可以放在接口,接口方法,类或是类方法上
  • 如果你希望被注解在方法上的事务和类的事务配置不同,如隔离级别或传播级别,那么就在方法上覆盖类的配置
  • 在代理模式中,只有通过代理进入的“外部”方法调用才会被截获。这意味着“自我调用”,即目标对象中调用目标对象的其他方法的方法,即使被调用的方法用@Transactional标记,也不会在运行时触发事务。

现在让我们了解一下@Transactional的属性: @Transactional (isolation=Isolation.READ_COMMITTED)

  • 默认值为Isolation.DEFAULT
  • 大多数场景下,使用默认值即可
  • 需要在事务开始之前配置。因为一旦事务开始,就无法进行配置

READ_COMMITTED 防止脏读;会发生不可重复的读取和幻读。

READ_UNCOMMITTED 会出现脏读,不可重复读和幻读。即可以看到事务尚未提交的数据

REPEATABLE_READ 可重复读。会出现幻读

序列化 防止脏读,幻读和不可重复读

@Transactional(timeout=60) 默认为底层事务系统的默认超时。 当事务超过该时间没有响应时,则会对底层系统发出回滚请求

@Transactional(propagation=Propagation.REQUIRED) 默认的传播级别为Required。其它的选项如REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER, 和NESTED

REQUIRED 表示如果当前没有活跃的事务上下文,目标方法将无法运行。如果在调用此方法之前已经启动了事务管理,那么它将在相同的事务中继续,或者在调用此方法时将立即开始新的事务。

REQUIRES_NEW 表示每次调用目标方法时都必须启动新的事务。如果已有事务,它将暂停。

MANDATORY 表示目标方法需要运行中的事务。如果没有事务,它将抛出异常。

SUPPORTS 无论是否有事务上下文,目标方法可以执行。如果当前有事务上下文,它将在同一个上下文中运行。如果没有,它仍将执行。这个选项适合获取数据的方法。

NOT_SUPPORTED 目标方法无需传播事务上下文。

NEVER 如果在事务上下文中执行目标方法,则抛出异常

@Transactional (rollbackFor=Exception.class)

  • 默认为rollbackFor=RunTimeException.class
  • 在spring中,这意味着只要事务上下文中抛出RuntimeException,事务就会回滚。
  • 可用于显示声明在某个异常出现时回滚

@Transactional (noRollbackFor=IllegalStateException.class) 如果该异常出现时,则不进行回滚

最后,也是最重要的一个问题,@Transactional注解究竟应该放在哪一层?Service层还是Dao层?

  • Service层是最合适的。服务层应该包含逻辑上进入事务的用户交互的详细级用例行为。
  • 在一些CRUD应用中,Service层的业务代码并不复杂,和Dao层的代码差不多。在这种场景下可以放置在DAO层
  • 如果在DAO层设置事务,而又有多个Service调用了DAO层的方法,那么将很难管理
  • 假如你的Service层是使用Hibernate在获取对象,而且你还使用懒加载获取集合。那么你需要在Service层开启事务,否则会抛出LazyInitializationException

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 记录分布式一致性中的几个概念

    事务是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元,狭义上的事务特指数据库事务。事务具有ACID属性。

    眯眯眼的猫头鹰
  • 猫头鹰的深夜翻译:如何优化MYSQL查询

    索引除了能够确保唯一的标记一条记录,还能是MySQL服务器更快的从数据库中获取结果。索引在排序中的作用也非常大。

    眯眯眼的猫头鹰
  • leetcode508. Most Frequent Subtree Sum

    Given the root of a tree, you are asked to find the most frequent subtree sum. T...

    眯眯眼的猫头鹰
  • Netty 解码器抽象父类 ByteToMessageDecoder 源码解析

    链接:https://www.jianshu.com/p/4c35541eec10

    用户5224393
  • ThingJS和传统3D开发的区别

    物联网3D可视化开发已经辐射到各行各业,无论车间还是消防,城市还是粮仓,亦或是地铁、科技园,物联网可视化是科技的进步,也是行业的进步。而传统的3D可视化开发实施...

    要不要吃火锅
  • 【每日一题】

    笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大! 这种方法的具体描述如下:...

    编程范 源代码公司
  • 提高查询数据速度

    在实际项目中,通过设计表架构时,设计系统结构时,查询数据时综合提高查询数据效率 1.适当冗余 数据库在设计时遵守三范式,同时业务数据(对数据的操作,比如资料...

    用户1558882
  • LeetCode 516. 最长回文子序列(动态规划)

    Michael阿明
  • 【算法】动态规划(二)

    上一篇,介绍了动态规划DP的概念,以及暴力递归和动态规划的关系。这一篇,将介绍几道经典的动态规划题

    MapleYe
  • MVC中的查询语句

    查询在MVC中做项目必不可少的,数据的新增、修改、删除都离不开查询。查询分为单表查询和多表查询两种(目前所学到的),单表查询是比较简单的,而多表就是比单表多了个...

    PHY_68

扫码关注云+社区

领取腾讯云代金券