前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >猫头鹰的深夜翻译:spring事务管理

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

作者头像
眯眯眼的猫头鹰
发布2018-10-31 11:13:52
5520
发布2018-10-31 11:13:52
举报

简介

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

管理事务的方法

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

1. 以编程方式,如下所示

代码语言:javascript
复制
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:

代码语言:javascript
复制
<!-- 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类:

代码语言:javascript
复制
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匿名类。

代码语言:javascript
复制
transactionTemplate.execute(new TransactionCallbackWithoutResult()
{    
    protected void doInTransactionWithoutResult(TransactionStatus status)
    {       
       updateOperation1();      
       updateOperation2();  
    } 
});
  • TransactionTemplate类的实例是线程安全的,这些实例不包含任何会话状态。
  • 然而TransactionTemplate实例确实会维持配置信息状态,所以即使多个类共享同一个TransactionTemplate实例,如果一个类需要使用另一种配置的TransactionTemplate(比如不同的隔离级别),那么就需要配置两个不同的实例。

b. 直接使用PlatformTransactionManager实现

代码语言:javascript
复制
<!-- 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>
代码语言:javascript
复制
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文件中定义事务管理器。

代码语言:javascript
复制
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>

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

代码语言:javascript
复制
<tx:annotation-driven transaction-manager="txManager"/>

或是在配置类中添加@EnableTransactionManagement

代码语言:javascript
复制
@Configuration
@EnableTransactionManagement
public class AppConfig
{
 ...
}

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

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

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

代码语言:javascript
复制
<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
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 管理事务的方法
    • 1. 以编程方式,如下所示
      • 2. 使用Spring管理事务
        • 编程式事务管理
          • 声明式事务管理(几乎用于所有web应用场景)
          相关产品与服务
          云数据库 SQL Server
          腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档