2016-08-23 2 views
7

У меня есть метод «databaseChanges», который вызывает итерации 2 операций: A, B. Сначала «A», «B» - последний. 'А' & 'B' может быть С reate, U бновить D далить функциональные возможности в моем постоянном хранении, база данных Oracle 11g.Откат назад, если B идет не так. spring boot, jdbctemplate

Скажем,

'A' обновить запись в таблице пользователей, атрибут почтового индекса, где ID = 1.

'B' вставить запись в таблицы хобби.

Сценарий: Метод базы данных вызван, «A» работает и обновляет запись. «B» работает и пытается вставить запись, что-то случилось, генерируется исключение, исключение кипит к методу databaseChanges.

Ожидаемое: «A» и «B» ничего не меняли. обновление, которое было сделано «A», будет откатом. «Б» ничего не изменил, ну ... было исключение.

Актуально: Обновление 'A', похоже, не откат. «Б» ничего не изменил, ну ... было исключение.


Некоторые Код

Если я имел связи, я хотел бы сделать что-то вроде:

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

Проблема: У меня нет подключения (см Tags что сообщение с вопросом)

Я пытался:

@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», не откат.

Из того, что я читал, я понимаю, что я не использую @Transactional правильно. Я прочитал и пробовал несколько сообщений в блогах и stackverflow Q & A без успеха для решения моей проблемы.

Любые предложения?


EDIT

Существует метод, который называют databaseChanges() метод

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

Какой метод должен быть аннотированный с @Transactional,

изменения()? databaseChanges()?

ответ

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?

+0

Постараюсь, спасибо! – lolo

+0

не работает. Обновление не откат – lolo

+0

Вы вводите класс (в моем примере MyFacade), правильно? В противном случае атрибут не будет соблюден. Возможно, вы можете создать очень простой пример и опубликовать все классы. Тогда легче понять, что еще не так. – Guenther

0

Попробуйте это:

@TransactionManagement(TransactionManagementType.BEAN) 
public class MyFacade { 

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

любое объяснение, почему это должен быть правильный ответ, было бы здорово! – Patrick

+0

Я попытался, получив: скомпилировать ошибку: TransactionManagement не может быть разрешено для типа. Мой класс аннотируется с @Service. – lolo

14

@Transactional аннотацию весной работ оборачивать ваш объект в прокси-сервер, который в свою очередь, оборачивает методы аннотированные с @Transactional в сделке. Из-за этого аннотации не будут работать на частные методы (как в вашем примере), потому что частные методы не могут быть унаследованы => они не могут быть завернуты (это неверно, если вы используете декларативные транзакции с aspectj, затем прокси- связанные с этим замечания ниже не применяются).

Вот основное объяснение того, как @Transactional Весенняя магия работает.

Вы писали:

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

Но это то, что вы на самом деле получаете, когда вы впрыснуть боб:

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() не расширенный метод «весеннего прокси» b(), поэтому транзакций не будет. В качестве обходного пути вы должны позвонить b() от другого bean-компонента.

Когда вы столкнулись с какой-либо из этих предостережений и не может использовать предлагаемый обходной путь (метод сделать без частного или вызвать b() из другого бина), вы можете использовать TransactionTemplate вместо декларативных транзакций:

public class A { 
    @Autowired 
    TransactionTemplate transactionTemplate; 

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

... 
} 

Update

Ответ на вопрос об обновленном OP, используя информацию выше.

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

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

Убедитесь changes() называется «снаружи» боба, а не из самого класса и после того, как контекст был экземпляр (например, это не afterPropertiesSet() или @PostConstruct аннотированный метод). Поймите, что весна откатывает транзакцию только для непроверенных исключений по умолчанию (попробуйте быть более конкретным в списке rollbackFor checked exceptions).

0

Что вам кажется недостающим является TransactionManager. Целью TransactionManager является управление транзакциями базы данных. Существует 2 типа транзакций, программных и декларативных. То, что вы описываете, является необходимостью декларативной транзакции через аннотации.

Так что вы должны быть на месте для вашего проекта следующие:

пружинные Операции Dependency (Использование Gradle в качестве примера)

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

Определение менеджера транзакций в Spring Конфигурация загрузки

Нечто подобное

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

Вам также необходимо добавить аннотацию @EnableTransactionManagement (не уверен, что это бесплатно в новых версиях весеннего ботинка.

@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 должны идти и как он ведет себя, так что лучше, чтобы получить что-то работает, а затем изучить эту область немного позже :)

В конце концов они находятся на месте (зависимость + TransactionManager конфигурация + аннотация), то транзакции должны работать соответственно.

Ссылки

Spring Reference Documentation on Transactions

Spring Guide for Transactions using Spring Boot - Это пример кода, который вы можете играть с

2

Any RuntimeException triggers rollback, and any checked Exception does not.

Это обычное поведение во всех интерфейсов Spring транзакций. По умолчанию, если изнутри транснационального кода выбрано RuntimeException, транзакция будет отклонена. Если выбрано исключенное исключение (т. Е. Не RuntimeException), транзакция не будет отменена.

Это зависит от того, какое исключение вы получаете внутри функции базы данных. Так что для того, чтобы поймать все исключения все, что вам нужно сделать, это добавить 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 
    } 
} 
0

Что вам нужно что-то вроде этого:

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

Указания распространения поскольку REQUIRES_NEW обеспечивает запуск новой транзакции для метода databaseChanges() и A() и B() в той же транзакции с их распространением, указанными как REQUIRED.

Вы должны убедиться, что методы вы аннотирования с @Transactional аннотаций являются открытыми, потому что транзакционный совет применяется только на общественных методов. Частный метод, аннотированный как таковой, NOT выдает ошибку, но будет NOITHER демонстрирует транзакционное поведение.

Теперь, когда исключение произойдет в B() при вставке, менеджер транзакций внутренне проверяет правила возврата (которые заданы функцией rollbackFor); он обнаруживает Exception.class и отмечает транзакцию (начатую на базе данныхChanges()) только в откате и откатывает A() вместе с ней, поскольку A() участвует в той же транзакции

Если это не решит вашу выпуск, включить журналы трассировки Springframework и предоставить мне это. Границы транзакций и события точно регистрируются в этих журналах, когда они включены.

Если вы не видите правильное ведение журнала транзакций, проверьте, действительно ли управление транзакциями включено в вашем приложении. Добавляет @EnableTransactionManagement в класс конфигурации.

Ссылки:
Propagation
RollbackFor
Transactional on private methods
@EnableTransactionManagement

Смежные вопросы