使用 @transactional注释在服务中回滚事务

发布于 2025-01-30 09:48:13 字数 4282 浏览 2 评论 0原文

晚上好,伙计们。

我有一种从端点调用的方法,可以将对象保存在数据库中,并且我想通过在服务内部发生任何例外情况,通过回滚交易来为此代码添加一些“保护”。

我将JAX-RS用于REST API部分,而OracleDB作为数据库。我的代码如下。

端点:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response createAcordo(AcordoDTO acordoDTO) {
    return acordosService.save(acordoDTO, token);
}

服务:

@Transactional(rollbackOn = Exception.class)
public Response save(AcordoDTO acordoDTO, String token) {
    try {
        ... some bisiness logic and validation

        Acordo acordo = acordoRepository.save(AcordoConverter.toAcordo(acordoDTO));
        UserDTO usr = getUserInfoFromToken();
        ApprovalDTO approvalDTO = new ApprovalDTO ();
        approvalDTO .setUser(usr.getName()); // this method is throwing null pointer exception  
                                             // because usr is null

        return Response.status(Response.Status.CREATED).entity(acordo).build();
       
    } catch (Exception e) {
        return new SystemErrorException().toResponse();
    }
}

尽管例外和 @transactional Anotation上面的代码正在将新的acordo对象保存到数据库中。但是,我期望发生的是完整的交易是回滚的,并且没有保存对象。 总而言之,我想知道的是:

  1. 我误解了有关 @transactional用法的任何概念吗?
  2. 如何修复此代码以实现所需的目标(回滚)?

ps.1 - 我尝试没有尝试/捕获块,结果是相同的。保存()仍然将对象提交到数据库中。

ps.2 - acordorepository.save()具有@transaction注释。

ps.3 - 我尝试添加transactionsynchronizationRegistry.setRollbackonly()在捕获块中,并且它工作。但是(对我来说)它看起来更像是作弊,而不是适当的解决方案。

ps.4 - 在我使用transactionsynchronizationRegistry.setrollbackonly()交易状态为status_marked_rollback,在所有其他测试中,它是status> status_active status> status_active status_marked_rollback /代码>。

更新1
我的acordorepository.save()如下。我试图检查是否有一个或两次转变。为此,我尝试(不确定是一个好主意)打印tsr.getTransactionKey()均在我的service.save.save()上。 acordorePository.save(),结果为相同的键,这使我相信只有一项交易。

@Resource
TransactionSynchronizationRegistry tsr; // I injected it here
                                        // for testing purposes
@Transactional
public T save(T entity) {
    if (entity.getId() == null) {
        entityManager.persist(entity);
    } else {
        entityManager.merge(entity);
    }

   entityManager.flush();

   return find(entity.getId());
}

更新2:
我的persistence.xml如下。

<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="namePU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <non-jta-data-source>java:/nameDS</non-jta-data-source>
    <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <jar-file>lib/client-notification-1.30.jar</jar-file>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
      <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
      <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform"/>
      <property name="hibernate.connection.autocommit" value="false"/>
      <property name="hibernate.show_sql" value="false"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="hibernate.generate_statistics" value="false"/>
      <!-- desabilita JSR-303 no save/update -->
      <property name="javax.persistence.validation.mode" value="none"/>
    </properties>
  </persistence-unit>
</persistence>

Best regards,
Thomaz.

Good evening, guys.

I have a method called from an endpoint that saves an objects on my database, and I'd like to add some "protection" to this code by rolling back the transaction in the case of any exception occur inside the service.

I'm using JAX-RS for the REST API part and OracleDB as the database. My code is as follows.

Endpoint:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response createAcordo(AcordoDTO acordoDTO) {
    return acordosService.save(acordoDTO, token);
}

Service:

@Transactional(rollbackOn = Exception.class)
public Response save(AcordoDTO acordoDTO, String token) {
    try {
        ... some bisiness logic and validation

        Acordo acordo = acordoRepository.save(AcordoConverter.toAcordo(acordoDTO));
        UserDTO usr = getUserInfoFromToken();
        ApprovalDTO approvalDTO = new ApprovalDTO ();
        approvalDTO .setUser(usr.getName()); // this method is throwing null pointer exception  
                                             // because usr is null

        return Response.status(Response.Status.CREATED).entity(acordo).build();
       
    } catch (Exception e) {
        return new SystemErrorException().toResponse();
    }
}

The code above, despite the exception and @Transactional anotation, is saving the new Acordo object to the database. What I'd expect to happen, however, is that the full transaction was rolledback and no objects were saved.
In summary, what I'd like to know is:

  1. I'm misundertaing any concepts regarding @Transactional usage ?
  2. How to fix this code to achieve de desired goal (rollback) ?

PS.1 - I tried without try/catch block and the result is the same. The save() still commits the object to the database.

PS.2 - The acordoRepository.save() has @Transaction annotation.

PS.3 - I tried adding TransactionSynchronizationRegistry.setRollbackOnly() inside the catch block and it worked. However (for me) it looks like more like a cheat than a proper solution.

PS.4 - After I used TransactionSynchronizationRegistry.setRollbackOnly() the transaction status was STATUS_MARKED_ROLLBACK and in all other tests it was STATUS_ACTIVE.

UPDATE 1
My acordoRepository.save() is as follows. I tried to check whether there was one or two transacions. In order to do that, I tried (not sure it's a good idea) to print the tsr.getTransactionKey() both on my Service.save() and acordoRepository.save() and the result was the same key, which led me to believe that there's only one transaction.

@Resource
TransactionSynchronizationRegistry tsr; // I injected it here
                                        // for testing purposes
@Transactional
public T save(T entity) {
    if (entity.getId() == null) {
        entityManager.persist(entity);
    } else {
        entityManager.merge(entity);
    }

   entityManager.flush();

   return find(entity.getId());
}

UPDATE 2:
My persistence.xml is as follows.

<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="namePU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <non-jta-data-source>java:/nameDS</non-jta-data-source>
    <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <jar-file>lib/client-notification-1.30.jar</jar-file>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
      <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
      <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform"/>
      <property name="hibernate.connection.autocommit" value="false"/>
      <property name="hibernate.show_sql" value="false"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="hibernate.generate_statistics" value="false"/>
      <!-- desabilita JSR-303 no save/update -->
      <property name="javax.persistence.validation.mode" value="none"/>
    </properties>
  </persistence-unit>
</persistence>

Best regards,
Thomaz.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

潜移默化 2025-02-06 09:48:13

如果您想要@transactional回滚方法,则不得在该方法中捕获异常。从保存中删除catch(异常),然后将其捕获在CreateAcordo中。那应该起作用。

If you want the @Transactional method to rollback you must not catch the exception inside that method. Remove the catch(Exception) from save and catch it in createAcordo instead. That should work.

ヤ经典坏疍 2025-02-06 09:48:13

可能是你可以尝试
throwable.class而不是expcy.class。由于throwable是超级类
顺便说一句,拜迪福特弹簧将负责回滚,但即使我们明确声明,它也会检查检查/未检查的例外
特别 - rollbackfor = {anyexception.class}检查的异常
您可以看到此示例: https .com/spring/transactional-rollbackfor-example-using-spring-boot/

may be you can try with
Throwable.class instead Exception.class since throwable is super class of if with remove try/catch
by the way bydefault spring will take care of rollBack but even if we are declaring explicitly then it will check for checked/unchecked exception
specifically- rollbackFor = {AnyException.class} for checked exception becuse runtimeException will take care by spring itself
you may look this example: https://www.netsurfingzone.com/spring/transactional-rollbackfor-example-using-spring-boot/

倥絔 2025-02-06 09:48:13

我无法对代码发表评论后whell :)
我正在做出答案:
我认为您可以使用这样的例外:

@Transactional(rollbackOn = CustomeException.class)
public Response save(AcordoDTO acordoDTO, String token) {
    try {
        ... some bisiness logic and validation
        if(! valid)
            throw new CustomeException();

        Acordo acordo = acordoRepository.save(AcordoConverter.toAcordo(acordoDTO));
        UserDTO usr = getUserInfoFromToken();
        ApprovalDTO approvalDTO = new ApprovalDTO ();
        approvalDTO .setUser(usr.getName()); // this method is throwing null pointer exception  
                                         // because usr is null
        // if this code throws any exception then throw runtime exception in your catch bloke
        return Response.status(Response.Status.CREATED).entity(acordo).build();
   
    } catch (Exception e) {
        throw new CustomeException();
    }
}

Whell after I couldn't make comment with code :)
I'm making an answer:
I think that you can just use an exception like this:

@Transactional(rollbackOn = CustomeException.class)
public Response save(AcordoDTO acordoDTO, String token) {
    try {
        ... some bisiness logic and validation
        if(! valid)
            throw new CustomeException();

        Acordo acordo = acordoRepository.save(AcordoConverter.toAcordo(acordoDTO));
        UserDTO usr = getUserInfoFromToken();
        ApprovalDTO approvalDTO = new ApprovalDTO ();
        approvalDTO .setUser(usr.getName()); // this method is throwing null pointer exception  
                                         // because usr is null
        // if this code throws any exception then throw runtime exception in your catch bloke
        return Response.status(Response.Status.CREATED).entity(acordo).build();
   
    } catch (Exception e) {
        throw new CustomeException();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文