3.4.5.3. 事务交互示例

嵌套事务的回滚

如果嵌套事务是通过 getTransaction() 创建并回滚,则无法提交外层事务。例如:

  1. void methodA() {
  2. Transaction tx = persistence.createTransaction();
  3. try {
  4. methodB(); (1)
  5. tx.commit(); (4)
  6. } finally {
  7. tx.end();
  8. }
  9. }
  10. void methodB() {
  11. Transaction tx = persistence.getTransaction();
  12. try {
  13. tx.commit(); (2)
  14. } catch (Exception e) {
  15. return; (3)
  16. } finally {
  17. tx.end();
  18. }
  19. }
1调用方法创建嵌套事务
2假设发生异常
3处理异常并退出
4在这里将抛出异常,因为事务被标记为仅回滚(rollback only)。

如果使用 createTransaction() 创建 methodB() 中的事务,那么回滚它将不会影响 methodA() 中的外层事务。

在嵌套事务中读取和修改数据

首先看一下使用 getTransaction() 创建的依赖嵌套事务:

  1. void methodA() {
  2. Transaction tx = persistence.createTransaction();
  3. try {
  4. EntityManager em = persistence.getEntityManager();
  5. Employee employee = em.find(Employee.class, id); (1)
  6. assertEquals("old name", employee.getName());
  7. employee.setName("name A"); (2)
  8. methodB(); (3)
  9. tx.commit(); (8)
  10. } finally {
  11. tx.end();
  12. }
  13. }
  14. void methodB() {
  15. Transaction tx = persistence.getTransaction();
  16. try {
  17. EntityManager em = persistence.getEntityManager(); (4)
  18. Employee employee = em.find(Employee.class, id); (5)
  19. assertEquals("name A", employee.getName()); (6)
  20. employee.setName("name B");
  21. tx.commit(); (7)
  22. } finally {
  23. tx.end();
  24. }
  25. }
1使用 name == “old name” 加载实体
2给字段设置新值
3调用方法创建嵌套事务
4获取与方法 methodA 中相同的 EntityManager 实例
5使用同样的标识符加载实体
6字段值是新的,因为我们使用相同的持久化上下文,并且根本没有调用 DB
7此时不进行实际的提交
8更改提交到 DB,它将包含 “name B”

现在,看一下使用 createTransaction() 创建的独立嵌套事务的相同示例:

  1. void methodA() {
  2. Transaction tx = persistence.createTransaction();
  3. try {
  4. EntityManager em = persistence.getEntityManager();
  5. Employee employee = em.find(Employee.class, id); (1)
  6. assertEquals("old name", employee.getName());
  7. employee.setName("name A"); (2)
  8. methodB(); (3)
  9. tx.commit(); (8)
  10. } finally {
  11. tx.end();
  12. }
  13. }
  14. void methodB() {
  15. Transaction tx = persistence.createTransaction();
  16. try {
  17. EntityManager em = persistence.getEntityManager(); (4)
  18. Employee employee = em.find(Employee.class, id); (5)
  19. assertEquals("old name", employee.getName()); (6)
  20. employee.setName("name B"); (7)
  21. tx.commit();
  22. } finally {
  23. tx.end();
  24. }
  25. }
1使用 name == “old name” 加载实体
2给字段设置新值
3调用方法创建嵌套事务
4创建新的 EntityManager 实例, 因为这是一个新事务
5使用相同的标识符加载一个实体
6字段值是旧的,因为一个旧的实体实例被从数据库加载了
7变更被提交到 DB,现在 “name B”值被存储到数据 DB
8由于启用乐观锁,这里将发生异常,提交失败

在最后一个例子中,只有当实体支持乐观锁时,即只有它实现了 Versioned 接口时,才会发生第(8)点的异常。