简介
- 大多数时候,开发者极少关注事务管理从而导致大量代码需要重新开发,或是实现事务的时候没有注意事务究竟是如何实现的以及在这些场景中需要关注的维度。
- 事务管理的一个重要方面是定义正确的事务边界,例如事务何时开始,什么时候应该结束,什么时候应该在数据库中提交数据,什么时候应该回滚(在出现异常的时候)。
- 对于开发人员而言,最重要的是了解如何在应用程序中更好的实现事务管理。所以现在让我们用不同的方式探索事务。
管理事务的方法
事务可以用以下方式管理:
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支持两类事务管理
- 编程式事务管理:这意味着必须在编程的帮助下管理事务。这提供了极大的灵活性,但很难维护。
- 声明式事务管理:意味着您将事务管理与业务代码分开。只能使用注释或基于XML的配置来管理事务。
强烈建议使用声明式事务。如果想知道其原因,请阅读下面的内容,否则,可以直接跳转到声明式事务管理实现的部分。
现在,让我们细致的分析每一种事务管理方法。
编程式事务管理
Spring Framework提供了两种编程式事务管理方法。
a. 使用TransactionTemplate (Spring推荐这种实现):
Context Xml file:
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
实现
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文件中定义事务管理器。
第二步:通过在spring应用程序上下文XML文件中添加以下条目,打开对事务注释的支持。
或是在配置类中添加@EnableTransactionManagement
@Configuration
@EnableTransactionManagement
public class AppConfig
{
...
}
Spring建议只使用@Transactional来注解具体类(以及具体类的方法),而不是接口。
原因是如果在接口上注解,并且使用基于类的代理(proxy-target-class="true")或是aop(mode="aspectj"),那么事务注解将无法被识别。
第三步:将注解添加在类(或是类的方法)或是接口(或是接口的方法上)
默认配置为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
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~