【问题标题】:JPA Nested Transactions all commit/rollback after runtime exceptionJPA嵌套事务在运行时异常后全部提交/回滚
【发布时间】:2019-01-14 11:28:07
【问题描述】:

经过一段时间阅读和测试不同形式的加载 EntityManager 实例和 bean 以进行一些嵌套事务,最后我没有找到一种方法,在运行时异常之后,数据完全回滚,必须接近阶段到什么我想要的是只有一个实体保留在数据库上。 情况如下: 我有一个类实体 Car 和一个类实体 CarPart,Car 的一个实例可以包含 CarPart 列表,我进行了测试以保持不变,当没有运行时错误出现时,一切都像一个魅力。问题是当抛出运行时错误时,例如数据库的触发错误控制器,有时对象会部分持久化。 我将 JPA 与 JavaEE 和 EclipseLink 一起使用。我需要一种方法来控制对象将完全持久化或回滚每个持久化操作。 任何帮助将不胜感激。

【问题讨论】:

    标签: java jpa jakarta-ee entitymanager


    【解决方案1】:

    JPA 中不支持嵌套事务,也没有已知的“正确”方式。数据库数据的任何部分回滚也需要对象状态的部分回滚,这通常是在异常发生后唯一明智的做法是将您的 EntityManager 和关联的对象扔掉。

    如果您使用 JavaEE 并将整个“用零件创建汽车”包装在单个事务中,则此行为应该可以立即使用。如果您使用的是单独的事务(正如我之前所说:没有办法让它们嵌套),那么汽车可能会被持久化,而零件则不会。

    显而易见的解决方案是对两者都使用一个容器管理的事务。

    【讨论】:

    • 感谢 fdreger 的回答。我已经测试了许多方法,包括单个容器管理器事务,但在某些情况下结果是相同的,汽车被持久化但零件没有。只有当我使用注解 @JoinColumn(name="CARS_ID", insertable = true, updatable = true) 时,其中一个部分失败了,所有部分都回滚了。
    • @LuisCarlos:这似乎是不可能的,而且将是一个重大错误。我坚信您一定是错误地创建了两个事务(例如,从一个 unmagaed 环境调用两个 EJB 方法)。如果您认为自己是正确的,请显示有问题的代码 :)
    • @LuisCarlos:正确处理事务是 JEE 堆栈的核心——它是从老式事务监视器演变而来的。添加神秘的 JPA 注释并不是使事务正常工作的必要条件。
    • 谢谢@fdreger 我发现我做错了什么;正如你所说,我错误地创建了多个交易。感谢您的宝贵时间。
    【解决方案2】:

    最后经过一段时间的测试,我找到了一种适用于我的场景的解决方案。 我没有发布所有代码,只发布与问题解决方案相对应的主要部分:

    经理类

    @ManagedBean
    @ApplicationScoped
    @TransactionManagement(TransactionManagementType.BEAN)
    public class CarsManager implements Serializable {
        @Resource
        private UserTransaction userTransaction;
        private CarsRepositorySameEM carsRepoSameEM;
        private CarsPartsRepositorySameEM carsPartsRepoSameEM;
        private EntityManager entityManager;
        private Cars car;
        private List<CarsParts> carPartList;
    
        @Inject
        public CarsManager(@MainEM EntityManager em) {
            this.entityManager = em;
            this.carsRepoSameEM = new CarsRepositorySameEM(this.entityManager);
            this.carsPartsRepoSameEM = new CarsPartsRepositorySameEM(this.entityManager);
        }
    
        public Cars getCar() {
            return car;
        }
    
        public void setCar(Cars car) {
            this.car = car;
        }
    
        public List<CarsParts> getCarPartList() {
            return carPartList;
        }
    
        public void setCarPartList(List<CarsParts> carPartList) {
           this.carPartList = carPartList;
        }
    
        public Boolean save() {
            Boolean result = Boolean.TRUE;
            try {
                this.userTransaction.begin();
                this.car = carsRepoSameEM.saveCar(this.car);
                if(carsPartsRepoSameEM.getEntityException().getCode() != 0) {
                   this.userTransaction.setRollbackOnly();
                }
                for (int i = 0; i < carPartList.size(); i++) {
                    carPartList.get(i).setCarsId(car.getID());
                }
                this.carPartList = carsPartsRepoSameEM.saveParts(carPartList);
                if(carsPartsRepoSameEM.getEntityException().getCode() != 0) {
                    this.userTransaction.setRollbackOnly();
                }
                car.setCarsPartsList(this.carPartList);
                this.userTransaction.commit();
            } catch (Exception e) {
                //e.printStackTrace();
                result = Boolean.FALSE;
                try {
                    this.userTransaction.rollback();
                } catch (Exception ex) {
                }
            }
            return result;
        }
    }
    

    存储库类:

    @Stateless
    public class CarsRepositorySameEM extends AbstractRepository<Cars, Long> {
        ...
        public Cars saveCar(Cars car) throws SystemException {
            try {
                this.entityManager.joinTransaction();
                car = this.create(car);
                this.entityManager.flush();
            } catch (Exception e) {
                this.setEntityException(GenericExceptionType.EX_JPA, -1, "CarsRepositorySameEM", "saveCar()", "Error al persistir el Car", null, e.getMessage());
                //throw e;
            }
            return car;
        }
    }
    

    -

    @Stateless
    public class CarsPartsRepositorySameEM extends AbstractRepository<CarsParts, Long> {
        ...
        public List<CarsParts> saveParts(List<CarsParts> carsPartsList) throws SystemException {
            try {
                this.entityManager.joinTransaction();
                for (int i = 0; i < carsPartsList.size(); i++) {
                    carsPartsList.set(i, this.create(carsPartsList.get(i)));
                }
                this.entityManager.flush();
            } catch (Exception e) {
                this.setEntityException(GenericExceptionType.EX_JPA, -1, "CarsPartsRepositorySameEM", "saveParts()", "Error al persistir las CarsParts", null, e.getMessage());
                //throw e;
            }
            return carsPartsList;
        }
    }
    

    观察:EntityManager 是从 Producer 方法注入的,该方法根据配置动态创建 EntityManagerFactory。

    正如 EclipseLink 文档中提到的用于配置将用于启用与主机容器集成的 ServerPlatform 一样,我必须在 persistence.xml 中放入属性

    <property name="eclipselink.target-server" value="Glassfish"/>
    

    使用此属性确保动态创建的 EntityManager 将使用事务类型 JTA。

    感谢答案,希望这个例子可以帮助任何面临类似问题的人。

    【讨论】:

    • 这太复杂了,其中一些也是完全错误的(比如创建一个 bean @Stateless 然后用 new 实例化它) - 超过 90% 是不必要的。当您需要事务时,只需在 @Stateless bean 中创建一个方法 - 它将在单个事务中执行。就是这样。
    • 我同意你的一些观点(也许你这么说是因为你不知道我的整个工作场景),但我不确定这是完全错误的解决方案,它也有构建对象存储库的不同形式的灵活性并动态设置管理与 DB 交互的 EntityManager。但是我意识到在 Java 中有不同的方法可以获得相同的结果。感谢您的意见。
    猜你喜欢
    • 2020-05-11
    • 2021-08-26
    • 1970-01-01
    • 2021-09-02
    • 1970-01-01
    • 2019-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多