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




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





  1. 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() 创建的依赖嵌套事务:





  1. 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() 创建的独立嵌套事务的相同示例:





  1. 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)点的异常。