Throw back A if B goes wrong. spring boot jdbctemplate

I have a databaseChanges method that iterates 2 operations: A, B. First, "A", "B" is the last. "A" and "B" can be C reate, U pdate D Elemental functions in my persistent storage, Oracle Database 11g.

Let's say

'A' update the entry in the Users table, zip attribute, where id = 1.

'B' insert record into hobby tables.

Scenario: The DatabaseChanges method is called, "A" is running and updating the record. "B" is working and trying to insert a record, something happened, an exception is thrown, the exception boils to the databaseChanges method.

Expected: "A" and "B" did not change anything. the update that was made by "A" will be a rollback. "B" didn't change anything, well ... there was an exception.

Actual: Update "A" does not seem to roll back. "B" didn't change anything, well ... there was an exception.


Some code

If I had a 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); } } 

Problem: I have no connection (see tags that are posted with the question)

I tried:

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

 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 an update. an exception is selected from "B". The update that was made by "A" is not a rollback.

From what I read, I understand that I am not using @Transactional correctly. I read and tried several blog posts and stackverflow Q and A without success to solve my problem.

Any suggestions?


EDIT

There is a method that calls databaseChanges ()

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

Which method should be annotated with @Transactional,

changes ()? databaseChanges ()?

+7
java oracle spring-boot spring-transactions jdbctemplate
source share
6 answers

@Transactional annotation in spring works by moving your object to a proxy server, which in turn wraps methods annotated with @Transactional in a transaction. Because of this, the annotation will not work on private methods (as in your example), since private methods cannot be inherited => they cannot be wrapped (this is not true if you use declarative transactions with aspectj , caveats related to proxies server below do not apply).

Here is a basic explanation of how @Transactional spring magic @Transactional .

You wrote:

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

But this is what you actually get when you enter a 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 do not work with @PostConstruct methods, because they are called before the object is @PostConstruct . And even if you configured everything correctly, transactions only roll back to unchecked exceptions by default. Use @Transactional(rollbackFor={CustomCheckedException.class}) if you need to rollback for some checked exception.

Another common disclaimer that I know:

@Transactional method will work only if you call it from the outside, in the following example b() will not be wrapped in a transaction:

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

This is also because @Transactional works by proxying your object. In the above example, a() will call Xb() non-extended spring proxy method " b() , so there will be no transaction. As a workaround, you must call b() from another bean.

When you come across any of these caveats and cannot use the proposed workaround (make the method non-personal or call b() from another bean), you can use TransactionTemplate instead of declarative transactions:

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

Update

Answering the question of the updated OP using the information above.

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

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

Make sure that changes() is called from the outside of the bean, not from the class itself and after the context has been created (for example, this is not the afterPropertiesSet() method or the @PostConstruct annotated method). Understand that a spring transaction cancels a transaction only for default exceptions (try being more specific on the rollbackFor checked exceptions list).

+14
source share

Any RuntimeException triggers a rollback, but not any exception thrown.

This is common behavior in all Spring Transaction APIs. By default , if a RuntimeException from within a transnational code, the transaction will be discarded. If an thrown exception is thrown (i.e. Not a RuntimeException ), the transaction will not be RuntimeException .

It depends on what kind of exception you get inside the DatabaseChanges function. Therefore, to catch all exceptions, all you have to do is add rollbackFor = Exception.class

The supposed change in the class of service, the code would be:

 @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 this by writing your own annotation:

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

The final code will be like this:

 @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
source share

The first code that you present is for UserTransactions, that is, the application must perform transaction management. Usually you want the container to take care of this and use the @Transactional annotation. I think that the problem in your case may be that you have an annotation for a private method. I would move the annotation to class level

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

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

+1
source share

Try the following:

 @TransactionManagement(TransactionManagementType.BEAN) public class MyFacade { @TransactionAttribute(TransactionAttribute.REQUIRES_NEW) public void databaseChanges() throws Exception { A(); //update. B(); //insert } 
0
source share

It seems that you are missing the TransactionManager . The goal of the TransactionManager is to manage database transactions. There are 2 types of transactions, programmatic and declarative. What you describe is the need for a declarative transaction through annotations.

So what you need for your project is the following:

Spring Transaction Dependency (Gradle Example)

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

Define Transaction Manager in Spring Boot Configuration

Something like that

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

You also need to add the @EnableTransactionManagement annotation (not sure if this is free on new versions of Spring).

 @EnableTransactionManagement public class AppConfig { ... } 

Add @Transactional

Here you must add the @Transactional annotation for the method you want to participate in the transaction

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

Please note that this method must be public , not private. You might want to consider hosting @Transactional in a public method that calls databaseChanges() .

There are also extended topics about where @Transactional should go and how it behaves, so it's better to start something first and then explore this area a bit later :)

After all this is in place (dependency + transaction manager configuration + annotation), then the transactions should work accordingly.

References

Spring Transaction Reference

Spring Spring Transaction Guide Download - There is sample code here that can be reproduced using

0
source share

What you need is:

 @Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor = {Exception.class}) public void databaseChanges() throws Exception { A(); //update. B(); //insert } @Transactional(propagation=Propagation.REQUIRED, rollbackFor = {Exception.class}) public void A() throws Exception { // update } @Transactional(propagation=Propagation.REQUIRED, rollbackFor = {Exception.class}) public void B() throws Exception { // insert } 

Setting the distribution as REQUIRES_NEW ensures that a new transaction is launched for the databaseChanges() method and A() and B() participate in the same transaction with their distribution specified as REQUIRED .

You need to make sure that the methods you comment using @Transactional annotations are publicly available, since transactional tips apply only to publicly available methods. A private method annotated as such will NOT throw an error, but NO shows transactional behavior.

Now, when an exception occurs in B() on insertion, the transaction manager checks the internal return rules (which are set by the rollbackFor function); it detects Exception.class and marks the transaction (started on the Changes () database) only as a rollback and rolls back A () with it, since A () is involved in the same transaction

If this does not solve your problem, enable the Springframework trace logs and provide me with this. Transaction boundaries and events are accurately logged in these logs at startup.

If you do not see the correct transaction logging, check to see if transaction management is really enabled in your application. Adding @EnableTransactionManagement to the configuration class allows this.

References:
Propagation
RollbackFor
Private Transaction
@EnableTransactionManagement

0
source share

All Articles