3.4.5.3. 事务交互示例
嵌套事务的回滚

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

void methodA() {
    Transaction tx = persistence.createTransaction();
    try {
        methodB(); (1)
        tx.commit(); (4)
    } finally {
        tx.end();
    }
}

void methodB() {
    Transaction tx = persistence.getTransaction();
    try {
        tx.commit(); (2)
    } catch (Exception e) {
        return; (3)
    } finally {
        tx.end();
    }
}
1 调用方法创建嵌套事务
2 假设发生异常
3 处理异常并退出
4 在这里将抛出异常,因为事务被标记为仅回滚(rollback only)。

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

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

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

void methodA() {
    Transaction tx = persistence.createTransaction();
    try {
        EntityManager em = persistence.getEntityManager();

        Employee employee = em.find(Employee.class, id); (1)
        assertEquals("old name", employee.getName());

        employee.setName("name A"); (2)

        methodB(); (3)

        tx.commit(); (8)
    } finally {
      tx.end();
    }
}

void methodB() {
    Transaction tx = persistence.getTransaction();
    try {
        EntityManager em = persistence.getEntityManager(); (4)

        Employee employee = em.find(Employee.class, id); (5)

        assertEquals("name A", employee.getName()); (6)
        employee.setName("name B");

        tx.commit(); (7)
    } finally {
      tx.end();
    }
}
1 使用 name == "old name" 加载实体
2 给字段设置新值
3 调用方法创建嵌套事务
4 获取与方法 methodA 中相同的 EntityManager 实例
5 使用同样的标识符加载实体
6 字段值是新的,因为我们使用相同的持久化上下文,并且根本没有调用 DB
7 此时不进行实际的提交
8 更改提交到 DB,它将包含 "name B"

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

void methodA() {
    Transaction tx = persistence.createTransaction();
    try {
        EntityManager em = persistence.getEntityManager();

        Employee employee = em.find(Employee.class, id); (1)
        assertEquals("old name", employee.getName());

        employee.setName("name A"); (2)

        methodB(); (3)

        tx.commit(); (8)
    } finally {
      tx.end();
    }
}

void methodB() {
    Transaction tx = persistence.createTransaction();
    try {
        EntityManager em = persistence.getEntityManager(); (4)

        Employee employee = em.find(Employee.class, id); (5)

        assertEquals("old name", employee.getName()); (6)

        employee.setName("name B"); (7)

        tx.commit();
    } finally {
      tx.end();
    }
}
1 使用 name == "old name" 加载实体
2 给字段设置新值
3 调用方法创建嵌套事务
4 创建新的 EntityManager 实例, 因为这是一个新事务
5 使用相同的标识符加载一个实体
6 字段值是旧的,因为一个旧的实体实例被从数据库加载了
7 变更被提交到 DB,现在 "name B"值被存储到数据 DB
8 由于启用乐观锁,这里将发生异常,提交失败

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