Java 如果 B 出错,则回滚 A。弹簧靴,jdbctemplate

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39096860/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:53:03  来源:igfitidea点击:

Roll back A if B goes wrong. spring boot, jdbctemplate

javaoraclespring-bootjdbctemplatespring-transactions

提问by lolo

I have a method, 'databaseChanges', which call 2 operations: A, B in iterative way. 'A' first, 'B' last. 'A' & 'B' can be Create, Update Delete functionalities in my persistent storage, Oracle Database 11g.

我有一个方法“databaseChanges”,它以迭代方式调用 2 个操作:A、B。“A”在前,“B”在后。“A”和“B”可以是Çreate,üPDATE d在我的持久存储elete功能,Oracle数据库11g。

Let's say,

让我们说,

'A' update a record in table Users, attribute zip, where id = 1.

'A' 更新表 Users 中的一条记录,属性为 zip,其中 id = 1。

'B' insert a record in table hobbies.

'B' 在表 hobbies 中插入一条记录。

Scenario:databaseChanges method is been called, 'A' operates and update the record. 'B' operates and try to insert a record, something happen, an exception is been thrown, the exception is bubbling to the databaseChanges method.

场景:databaseChanges 方法被调用,'A' 操作并更新记录。'B' 操作并尝试插入一条记录,发生了一些事情,抛出了异常,异常冒泡到 databaseChanges 方法。

Expected:'A' and 'B' didn't change nothing. the update which 'A' did, will be rollback. 'B' didn't changed nothing, well... there was an exception.

预期:“A”和“B”没有任何改变。'A' 所做的更新将被回滚。'B' 没有任何改变,嗯……有一个例外。

Actual:'A' update seems to not been rolled back. 'B' didn't changed nothing, well... there was an exception.

实际:“A”更新似乎没有回滚。'B' 没有任何改变,嗯……有一个例外。



Some Code

一些代码

If i had the connection, i would do something like:

如果我有连接,我会做类似的事情:

private void databaseChanges(Connection conn) {
   try {
          conn.setAutoCommit(false);
          A(); //update.
          B(); //insert
          conn.commit();
   } catch (Exception e) { 
        try {
              conn.rollback();
        } catch (Exception ei) {
                    //logs...
        }
   } finally {
          conn.setAutoCommit(true);
   }
}

The problem:I don't have the connection (see the Tags that post with the question)

问题:我没有连接(请参阅随问题发布的标签)

I tried to:

我试过了:

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @Transactional
    private void databaseChanges() throws Exception {   
        A(); //update.
        B(); //insert
    }
}


My AppConfig class:

我的 AppConfig 类:

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

@Configuration
public class AppConfig {    
    @Autowired
    private DataSource dataSource;

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
        return new NamedParameterJdbcTemplate(dataSource);
    }   
}


'A' makes the update. from 'B' an exception is been thrown. The update which been made by 'A' is not been rolled back.

'A' 进行更新。从 'B' 抛出异常。'A' 所做的更新不会被回滚。

From what i read, i understand that i'm not using the @Transactional correctly. I read and tried several blogs posts and stackverflow Q & A without succeess to solve my problem.

从我读到的,我明白我没有正确使用@Transactional。我阅读并尝试了几篇博客文章和 stackverflow 问答,但都没有成功解决我的问题。

Any suggestions?

有什么建议?



EDIT

编辑

There is a method that call databaseChanges() method

有一个方法调用 databaseChanges() 方法

public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}

Which method should be annotated with @Transactional,

哪个方法应该用@Transactional 注释,

changes()? databaseChanges()?

变化()?数据库更改()?

采纳答案by frenzykryger

@Transactionalannotation in spring works by wrapping your object in a proxy which in turn wraps methods annotated with @Transactionalin a transaction. Because of that annotation will not work on private methods (as in your example) because private methods can't be inherited=> they can't be wrapped (this is not true if you use declarative transactions with aspectj, then proxy-related caveats below don't apply).

@Transactionalspring 中的注释通过将对象包装在代理中来工作,代理又包装@Transactional在事务中注释的方法。由于该注释不适用于私有方法(如您的示例中),因为私有方法不能被继承=> 它们不能被包装(如果您使用带有aspectj 的声明性事务,则情况并非如此,然后是代理相关的警告以下不适用)。

Here is basic explanation of how @Transactionalspring magic works.

这是对@Transactional弹簧魔法如何工作的基本解释。

You wrote:

你写了:

class A {
    @Transactional
    public void method() {
    }
}

But this is what you actually get when you inject a bean:

但这就是你注入 bean 时实际得到的结果:

class ProxiedA extends A {
   private final A a;

   public ProxiedA(A a) {
       this.a = a;
   }

   @Override
   public void method() {
       try {
           // open transaction ...
           a.method();
           // commit transaction
       } catch (RuntimeException e) {
           // rollback transaction
       } catch (Exception e) {
           // commit transaction
       }
   }
} 

This has limitations. They don't work with @PostConstructmethods because they are called before object is proxied. And even if you configured all correctly, transactions are only rolled back on uncheckedexceptions by default. Use @Transactional(rollbackFor={CustomCheckedException.class})if you need rollback on some checked exception.

这有局限性。它们不适用于@PostConstruct方法,因为它们在对象被代理之前被调用。即使您正确配置了所有内容,默认情况下事务只会在未检查的异常时回滚。使用@Transactional(rollbackFor={CustomCheckedException.class})如果您需要回滚一些检查异常。

Another frequently encountered caveat I know:

我知道的另一个经常遇到的警告:

@Transactionalmethod will only work if you call it "from outside", in following example b()will not be wrapped in transaction:

@Transactional方法仅在您“从外部”调用它时才有效,在以下示例中b()不会包含在事务中:

class X {
   public void a() {
      b();
   }

   @Transactional
   public void b() {
   }
}

It is also because @Transactionalworks by proxying your object. In example above a()will call X.b()not a enhanced "spring proxy" method b()so there will be no transaction. As a workaround you have to call b()from another bean.

这也是因为@Transactional通过代理您的对象来工作。在上面的示例中,a()不会调用X.b()增强的“spring 代理”方法,b()因此不会有事务。作为一种解决方法,您必须b()从另一个 bean调用。

When you encountered any of these caveats and can't use a suggested workaround (make method non-private or call b()from another bean) you can use TransactionTemplateinstead of declarative transactions:

当您遇到任何这些警告并且无法使用建议的解决方法(使方法非私有或b()从另一个 bean调用)时,您可以使用TransactionTemplate而不是声明性事务:

public class A {
    @Autowired
    TransactionTemplate transactionTemplate;

    public void method() {
        transactionTemplate.execute(status -> {
            A();
            B();
            return null;
        });
    }

...
} 

Update

更新

Answering to OP updated question using info above.

使用上面的信息回答 OP 更新的问题。

Which method should be annotated with @Transactional: changes()? databaseChanges()?

哪个方法应该用@Transactional:changes() 注释?数据库更改()?

@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}

Make sure changes()is called "from outside" of a bean, not from class itself and after context was instantiated (e.g. this is not afterPropertiesSet()or @PostConstructannotated method). Understand that spring rollbacks transaction only for unchecked exceptions by default (try to be more specific in rollbackFor checked exceptions list).

确保changes()从 bean 的“外部”调用,而不是从类本身和在实例化上下文之后调用(例如,这不是 afterPropertiesSet()或带@PostConstruct注释的方法)。了解 spring 回滚事务默认仅针对未检查的异常(尝试在 rollbackFor 已检查的异常列表中更具体)。

回答by Guenther

The first code you present is for UserTransactions, i.e. the application has to do the transaction management. Usually you want the container to take care of that and use the @Transactional annotation. I think the problem in you case might be, that you have the annotation on a private method. I'd move the annotation to the class level

您提供的第一个代码用于 UserTransactions,即应用程序必须进行事务管理。通常您希望容器负责并使用 @Transactional 注释。我认为你的问题可能是,你有一个私有方法的注释。我会将注释移至类级别

@Transactional
public class MyFacade {

public void databaseChanges() throws Exception {   
    A(); //update.
    B(); //insert
}

Then it should rollback properly. You can find more details here Does Spring @Transactional attribute work on a private method?

然后它应该正确回滚。您可以在此处找到更多详细信息 Spring @Transactional 属性是否适用于私有方法?

回答by user3805841

Try this:

尝试这个:

@TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {

@TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {   
    A(); //update.
    B(); //insert
}

回答by Shiraaz.M

What you seem to be missing is a TransactionManager. The purpose of the TransactionManageris to be able to manage database transactions. There are 2 types of transactions, programmatic and declarative. What you are describing is a need for a declarative transaction via annotations.

你似乎缺少的是一个TransactionManager. 的目的TransactionManager是能够管理数据库事务。有两种类型的事务,编程式和声明式。您所描述的是需要通过注释进行声明性事务。

So what you need to be in place for your project is the following:

因此,您需要为您的项目准备以下内容:

Spring Transactions Dependency (Using Gradle as example)

Spring Transactions 依赖(以 Gradle 为例)

compile("org.springframework:spring-tx")

Define a Transaction Manager in Spring Boot Configuration

在 Spring Boot 配置中定义事务管理器

Something like this

像这样的东西

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
    return new DataSourceTransactionManager(dataSource);
}

You would also need to add the @EnableTransactionManagementannotation (not sure if this is for free in newer versions of spring boot.

您还需要添加@EnableTransactionManagement注释(不确定在较新版本的 spring boot 中这是否免费。

@EnableTransactionManagement
public class AppConfig {
...
}

Add @Transactional

添加@Transactional

Here you would add the @Transactionalannotation for the method that you want to participate in the transaction

在这里,您将为@Transactional要参与事务的方法添加注释

@Transactional
public void book(String... persons) {
    for (String person : persons) {
        log.info("Booking " + person + " in a seat...");
        jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
    }
};

Note that this method should be publicand not private. You may want to consider putting @Transactionalon the public method calling databaseChanges().

请注意,此方法应该是公共的而不是私有的。您可能需要考虑@Transactional使用公共方法调用databaseChanges()

There are also advanced topics about where @Transactionalshould go and how it behaves, so better to get something working first and then explore this area a bit later:)

还有一些关于@Transactional应该去哪里以及它如何表现的高级主题,所以最好先让一些东西工作,然后再探索这个领域:)

After all these are in place (dependency + transactionManager configuration + annotation), then transactions should work accordingly.

在所有这些都到位后(依赖项 + transactionManager 配置 + 注释),事务应该相应地工作。

References

参考

Spring Reference Documentation on Transactions

关于事务的 Spring 参考文档

Spring Guide for Transactions using Spring Boot- This has sample code that you can play with

Spring Guide for Transactions using Spring Boot- 这有您可以使用的示例代码

回答by choop

Any RuntimeExceptiontriggers rollback, and any checked Exception does not.

任何RuntimeException触发器都会回滚,而任何已检查的异常都不会。

This is common behavior across all Spring transaction APIs. By default, if a RuntimeExceptionis thrown from within the transactional code, the transaction will be rolled back. If a checked exception (i.e. not a RuntimeException) is thrown, then the transaction will not be rolled back.

这是所有 Spring 事务 API 的常见行为。默认情况下,如果RuntimeException从事务代码中抛出a ,事务将被回滚。如果RuntimeException抛出已检查的异常(即不是 a ),则事务将不会回滚。

It depends on which exception you are getting inside databaseChangesfunction. So in order to catch all exceptions all you need to do is to add rollbackFor = Exception.class

这取决于您在databaseChanges函数内部遇到的异常。因此,为了捕获所有异常,您需要做的就是添加rollbackFor = Exception.class

The change supposed to be on the service class, the code will be like that:

应该在服务类上进行更改,代码将如下所示:

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @Transactional(rollbackFor = Exception.class)
    private void databaseChanges() throws Exception {   
        A(); //update
        B(); //insert
    }
}

In addition you can do something nice with it so not all the time you will have to write rollbackFor = Exception.class. You can achieve that by writing your own custom annotation:

此外,您可以用它做一些不错的事情,因此您不必总是写rollbackFor = Exception.class。您可以通过编写自己的自定义注释来实现这一点:

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor = Exception.class)
@Documented
public @interface CustomTransactional {  
}

The final code will be like that:

最终的代码将是这样的:

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @CustomTransactional
    private void databaseChanges() throws Exception {   
        A(); //update
        B(); //insert
    }
}