JPA和事务管理

很重要的一点是JPA本身并不提供任何类型的声明式事务管理。如果在依赖注入容器之外使用JPA,事务处理必须由开发人员编程实现。

1
2
3
4
5
6
7
8
9
10
11
12
UserTransaction utx = entityManager.getTransaction();

try {
utx.begin();

businessLogic();

utx.commit();
} catch(Exception ex) {
utx.rollback();
throw ex;
}
这种方式的事务管理使事务范围可以在代码中很清晰地表达出来,但它有以下缺点:

容易出现重复代码和错误
任何错误可能产生较大的影响
错误难以调试和复现
降低了代码库的可读性
如果该方法调用了其他的事务方法如何处理呢?
使用Spring @Transactional

使用Spring @Transactional,上面的代码就简化为:

1
2
3
4
@Transactional
public void businessLogic() {
... use entity manager inside a transaction ...
}
代码更加简洁,可读性更好,也是目前Spring中事务处理的推荐方式。

通过使用@Transactional,事务传播等很多重要方面可以自动处理。这种情况下如果businessLogic()调用了其他事务方法,该方法将根据选项确定如何加入正在运行事务。

这个强大机制的一个潜在缺点是它隐藏了底层的运行,当它不能正常工作时很难调试。

@Transactional含义

关于@Transactional,关键点之一是要考虑两个独立的概念,它们都有各自的范围和生命周期:

persistence context(持久化上下文)
database transaction(事务)
@Transactional本身定义了单个事务的范围。这个事务在persistence context的范围内。

JPA中的持久化上下文是EntityManager,内部实现使用了Hibernate Session(使用Hibernate作为持久化provider)。

持久化上下文仅仅是一个同步对象,它记录了有限集合的Java对象的状态,并且保证这些对象的变化最终持久化到数据库。

这是与单个事务非常不同的概念。一个Entity Manager可以跨越多个事务使用,而且的确是这样使用的。

EntityManager何时跨越多个事务?

最常见的情况是应用使用Open Session In View模式处理懒初始化异常时,之前的文章介绍过这种做法的优势和劣势。

这种情况下视图层运行的多个查询处于独立的事务中,而不是单事务的业务逻辑,但这些查询由相同的entity manager管理。

另一种情况是开发人员将持久化上下文标记为PersistenceContextType.EXTENDED,这表示它能够响应多个请求。

如何定义EntityManager和Transaction之间的关系?

这由应用开发者来选择,但是JPA Entity Manager最常用的方式是“Entity Manager per application transaction”(每个事务都有自己的实体管理器)模式。entity manager注入的常用方法是:

1
2
@PersistenceContext
private EntityManager em;
这里默认为“Entity Manager per transaction”模式。这种模式下如果在@Transactional方法内部使用该Entity Manager,那么该方法将在单一事务中运行。

@PersistenceContext如何工作?

随之而来的问题就是@PersistenceContext如何仅在容器启动时注入entity manager,假定entity manager生命周期很短暂,而且每次请求需要多个entity manager。

答案是它不能:EntityManager是一个接口,注入到spring bean中的不是entity manager本身,而是在运行时代理具体entity manager的context aware proxy(上下文感知代理)。

通常用于代理的具体类为SharedEntityManagerInvocationHandler,借助调试器可以确认这一点。

那么@Transactional如何工作?

实现了EntityManager接口的持久化上下文代理并不是声明式事务管理的唯一部分,事实上包含三个组成部分:

EntityManager Proxy本身
事务的切面
事务管理器
看一下这三部分以及它们之间的相互作用。

事务的切面

事务的切面是一个“around(环绕)”切面,在注解的业务方法前后都可以被调用。实现切面的具体类是TransactionInterceptor。

事务的切面有两个主要职责:

在’before’时,切面提供一个调用点,来决定被调用业务方法应该在正在进行事务的范围内运行,还是开始一个新的独立事务。
在’after’时,切面需要确定事务被提交,回滚或者继续运行。
在’before’时,事务切面自身不包含任何决策逻辑,是否开始新事务的决策委派给事务管理器完成。

事务管理器

事务管理器需要解决下面两个问题:

新的Entity Manager是否应该被创建?
是否应该开始新的事务?
这些需要事务切面’before’逻辑被调用时决定。事务管理器的决策基于以下两点:

事务是否正在进行
事务方法的propagation属性(比如REQUIRES_NEW总要开始新事务)
如果事务管理器确定要创建新事务,那么将:

创建一个新的entity manager
entity manager绑定到当前线程
从数据库连接池中获取连接
将连接绑定到当前线程
使用ThreadLocal变量将entity manager和数据库连接都绑定到当前线程。

事务运行时他们存储在线程中,当它们不再被使用时,事务管理器决定是否将他们清除。

程序的任何部分如果需要当前的entity manager和数据库连接都可以从线程中获取。

EntityManager proxy

EntityManager proxy(前面已经介绍过)就是谜题的最后一部分。当业务方法调用entityManager.persist()时,这不是由entity manager直接调用的。

而是业务方法调用代理,代理从线程获取当前的entity manager,前面介绍过事务管理器将entity manager绑定到线程。

了解了@Transactional机制的各个部分,我们来看一下实现它的常用Spring配置。

整合三个部分

如何将三个部分组合起来使事务注解可以正确地发挥作用呢?首先定义entity manager工厂。

这样就可以通过持久化上下文注解注入Entity Manager proxy。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class EntityManagerFactoriesConfiguration {
@Autowired
private DataSource dataSource;

@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean emf() {
LocalContainerEntityManagerFactoryBean emf = ...
emf.setDataSource(dataSource);
emf.setPackagesToScan(
new String[] {"your.package"});
emf.setJpaVendorAdapter(
new HibernateJpaVendorAdapter());
return emf;
}
}
下一步实现配置事务管理器和在@Transactional注解的类中应用事务的切面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
@EnableTransactionManagement
public class TransactionManagersConfig {
@Autowired
EntityManagerFactory emf;
@Autowired
private DataSource dataSource;

@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm =
new JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return tm;
}
}
注解@EnableTransactionManagement通知Spring,@Transactional注解的类被事务的切面包围。这样@Transactional就可以使用了。

总结

Spring声明式事务管理机制非常强大,但它可能被误用或者容易发生配置错误。

当这个机制不能正常工作或者未达到预期运行结果等问题出现时,理解它的内部工作情况是很有帮助的。

需要记住的最重要的一点是,要考虑到两个概念:事务和持久化上下文,每个都有自己不可读的明显的生命周期。

JPA和事务管理的更多相关文章

  1. Spring注入JPA+JPA事务管理

    本例实现的是Spring注入JPA 和 使用JPA事务管理.JPA是sun公司开发的一项新的规范标准.在本质上来说,JPA可以看作是Hibernate的一个子集:然而从功能上来说,Hibernate是 ...

  2. spring05-Spring事务管理

    事务的第一个方面是传播行为(propagation behavior).当事务方法被另一个事务方法调用时,必须指定事务应该如何传播.例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的 ...

  3. Spring 对事务管理的支持

    1.Spring对事务管理的支持 Spring为事务管理提供了一致的编程模板,在高层次建立了统一的事务抽象.也就是说,不管选择Spring JDBC.Hibernate .JPA 还是iBatis,S ...

  4. 【spring源码学习】spring的事务管理的源码解析

    [一]spring事务管理(1)spring的事务管理,是基于aop动态代理实现的.对目标对象生成代理对象,加入事务管理的核心拦截器==>org.springframework.transact ...

  5. 深入学习Spring框架(四)- 事务管理

    1.什么是事务? 事务(Transaction)是一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位.事务是为了保证数据库的完整性.例如:A给B转账,需 ...

  6. 09.事务管理、整合jpa、整合mybatis

    事务管理 spring-boot-starter-jdbc会自动默认注入DataSourceTransactionManager spring-boot-starter-data-jpa会自动默认注入 ...

  7. Spring的事务管理

    事务 事务:是逻辑上一组操作,要么全都成功,要么全都失败. 事务特性(ACID) 原子性:事务不可分割 一致性:事务执行的前后,数据完整性保持一致 隔离性:一个事务执行的时候,不应该受到其他事务的打扰 ...

  8. Spring对事务管理的支持的发展历程--转

    原文地址:http://www.iteye.com/topic/1123049 1.问题 Connection conn = DataSourceUtils.getConnection(); //开启 ...

  9. 全面分析 Spring 的编程式事务管理及声明式事务管理

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

随机推荐

  1. aardio 开发桌面应用,这几点必须要掌握!

    1. 前言 大家好,我是安果! 上一篇文章写到可以通过 aardio 结合 Python 开发桌面应用,有些小伙伴后台给我留言,说 Aardio 资料太少,希望我能补充一些实用的功能 实用 | 利用 ...

  2. 攻防世界 WEB 高手进阶区 TokyoWesterns CTF shrine Writeup

    攻防世界 WEB 高手进阶区 TokyoWesterns CTF shrine Writeup 题目介绍 题目考点 模板注入 Writeup 进入题目 import flask import os a ...

  3. ES6遍历对象方法

    ES6 一共有 5 种方法可以遍历对象的属性. (1)for...in for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性). let obj = {a:1,b:2,c:3 ...

  4. storm提交拓扑报错processing getcomponentpendingprofileactions

    storm提交新的拓扑,拓扑能提交成功,但是在UI界面查看时每个bolt报错Thrift.processing getComponentPendingProfileActions异常. 原因:stor ...

  5. 通过大量实战案例分解Netty中是如何解决拆包黏包问题的?

    TCP传输协议是基于数据流传输的,而基于流化的数据是没有界限的,当客户端向服务端发送数据时,可能会把一个完整的数据报文拆分成多个小报文进行发送,也可能将多个报文合并成一个大报文进行发送. 在这样的情况 ...

  6. CODING添加ssh提示格式错误的问题

    不能去.shh文件夹打开id_rsa.pub文件查看 解决方法: 进入.ssh文件夹,然后右键git bash here 输入代码 cat id_rsa.pub 回车即可

  7. 手把手教你学Dapr - 6. 发布订阅

    上一篇:手把手教你学Dapr - 5. 状态管理 介绍 发布/订阅模式允许微服务使用消息相互通信.生产者或发布者在不知道哪个应用程序将接收它们的情况下向主题发送消息.这涉及将它们写入输入通道.同样,消 ...

  8. 保姆级别的vue + ElementUI 搭建后台管理系统教程

    vue + ElementUI 搭建后台管理系统记录 本文档记录了该系统从零配置的完整过程 项目源码请访问:https://gitee.com/szxio/vue2Admin,如果感觉对你有帮助,请点 ...

  9. dart系列之:在dart中使用数字和字符串

    目录 简介 数字 字符串 StringBuffer 总结 简介 要想熟悉一种语言,最简单的做法就是熟悉dart提供的各种核心库.dart为我们提供了包括dart:core,dart:async,dar ...

  10. 测试平台系列(81) 编写在线执行Redis功能

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上一节我们牛刀小试,编写了 ...