如果B出错,请回退A. spring boot,jdbctemplate

时间:2016-08-23 09:05:04

标签: java oracle spring-boot jdbctemplate spring-transactions

我有一个方法,' databaseChanges',它以迭代的方式调用2个操作:A,B。 ' A'首先,' B'持续。 ' A' &安培; ' B'在我的持久存储Oracle Database 11g中可以 C reate, U pdate D elete功能。

让我们说,

' A'更新表中的记录用户,属性zip,其中id = 1。

' B'在表爱好中插入记录。

场景:已调用databaseChanges方法,' A'操作和更新记录。 ' B'操作并尝试插入记录,发生一些事情,抛出异常,异常是冒充问databaseChanges方法。

预期:' A'和' B'什么都没改变。更新“A'做了,将会回滚。 ' B'没有改变,嗯...有一个例外。

实际:' A'更新似乎没有被回滚。 ' B'没有改变,嗯...有一个例外。

部分代码

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

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);
   }
}

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

我试图:

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

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

我的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'进行更新。来自' B'抛出异常。由A' A'做出的更新没有回滚。

根据我的阅读,我明白我没有正确使用@Transactional。 我阅读并尝试了几篇博客文章和stackverflow Q& A没有成功解决我的问题。

有什么建议吗?

修改

有一种方法可以调用databaseChanges()方法

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

哪种方法应使用@Transactional注释,

的变化()? databaseChanges()?

5 个答案:

答案 0 :(得分:19)

Spring中的

@Transactional注释通过将对象包装在代理中来工作,而代理又在事务中包装带有@Transactional注释的方法。因为注释不适用于私有方法(如在您的示例中),因为私有方法不能被继承 =>他们不能被包裹(如果您使用aspectj的声明性交易,则不是这样,那么下面的代理相关警告不适用)。

以下是@Transactional春天魔法如何运作的基本解释。

您写道:

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

但是这是你注入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
       }
   }
} 

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

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

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

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

   @Transactional
   public void b() {
   }
}

这也是因为@Transactional通过代理您的对象来工作。在上面的示例中,a()将调用X.b()而不是增强的" spring代理"方法b()所以没有交易。作为一种解决方法,您必须从另一个bean调用b()

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

public class A {
    @Autowired
    TransactionTemplate transactionTemplate;

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

...
} 

<强>更新

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

  

应使用@Transactional注释哪个方法:   变化()? databaseChanges()?

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

确保changes()被呼叫&#34;来自外部&#34;一个bean,而不是来自类本身,并且在实例化上下文之后(例如,这不是afterPropertiesSet()@PostConstruct注释方法)。默认情况下,只知道未经检查的异常的spring rollbacks事务(尝试在rollbackFor checked exception列表中更具体)。

答案 1 :(得分:4)

  

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

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

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

应该在服务类上进行更改,代码将是这样的:

@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
    }
}

此外,你可以做一些不错的事情,所以不是所有的时间你都要写rollbackFor = Exception.class。您可以通过编写自己的自定义注释来实现这一目标:

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

最终代码将是这样的:

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

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

答案 2 :(得分:1)

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

@Transactional
public class MyFacade {

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

然后它应该正确回滚。你可以在这里找到更多细节 Does Spring @Transactional attribute work on a private method?

答案 3 :(得分:0)

试试这个:

@TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {

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

答案 4 :(得分:0)

您似乎缺少的是TransactionManagerTransactionManager的目的是能够管理数据库事务。有两种类型的交易,程序化和声明式。您所描述的是需要通过注释进行声明性事务。

因此,您需要为项目制定以下内容:

Spring Transactions依赖关系(以Gradle为例)

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

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

像这样的东西

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

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

@EnableTransactionManagement
public class AppConfig {
...
}

添加@Transactional

在这里,您要为要参与交易的方法添加@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);
    }
};

请注意,此方法应该是公开的而不是私有的。您可以考虑将@Transactional放在调用databaseChanges()的公共方法上。

还有关于@Transactional应该去哪里以及它的行为方式的高级主题,所以最好先让某些东西先行,然后稍后探索这个区域:)

完成所有这些(依赖+事务管理器配置+注释)之后,事务应该相应地工作。

<强>参考

Spring Reference Documentation on Transactions

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