Меню

Ошибка сообщение transaction rolled back because it has been marked as rollback only

I have this scenario:

  1. fetch (read and delete) a record from IncomingMessage table
  2. read record content
  3. insert something to some tables
  4. if an error (any exception) occurred in steps 1-3, insert an error-record to OutgoingMessage table
  5. otherwise, insert an success-record to OutgoingMessage table

So steps 1,2,3,4 should be in a transaction, or steps 1,2,3,5

My process starts from here (it is a scheduled task):

public class ReceiveMessagesJob implements ScheduledJob {
// ...
    @Override
    public void run() {
        try {
            processMessageMediator.processNextRegistrationMessage();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
// ...
}

My main function (processNextRegistrationMessage) in ProcessMessageMediator:

public class ProcessMessageMediatorImpl implements ProcessMessageMediator {
// ...
    @Override
    @Transactional
    public void processNextRegistrationMessage() throws ProcessIncomingMessageException {
        String refrenceId = null;
        MessageTypeEnum registrationMessageType = MessageTypeEnum.REGISTRATION;
        try {
            String messageContent = incomingMessageService.fetchNextMessageContent(registrationMessageType);
            if (messageContent == null) {
                return;
            }
            IncomingXmlModel incomingXmlModel = incomingXmlDeserializer.fromXml(messageContent);
            refrenceId = incomingXmlModel.getRefrenceId();
            if (!StringUtil.hasText(refrenceId)) {
                throw new ProcessIncomingMessageException(
                        "Can not proceed processing incoming-message. refrence-code field is null.");
            }
            sqlCommandHandlerService.persist(incomingXmlModel);
        } catch (Exception e) {
            if (e instanceof ProcessIncomingMessageException) {
                throw (ProcessIncomingMessageException) e;
            }
            e.printStackTrace();
            // send error outgoing-message
            OutgoingXmlModel outgoingXmlModel = new OutgoingXmlModel(refrenceId,
                    ProcessResultStateEnum.FAILED.getCode(), e.getMessage());
            saveOutgoingMessage(outgoingXmlModel, registrationMessageType);
            return;
        }
        // send success outgoing-message
        OutgoingXmlModel outgoingXmlModel = new OutgoingXmlModel(refrenceId, ProcessResultStateEnum.SUCCEED.getCode());
        saveOutgoingMessage(outgoingXmlModel, registrationMessageType);
    }

    private void saveOutgoingMessage(OutgoingXmlModel outgoingXmlModel, MessageTypeEnum messageType)
            throws ProcessIncomingMessageException {
        String xml = outgoingXmlSerializer.toXml(outgoingXmlModel, messageType);
        OutgoingMessageEntity entity = new OutgoingMessageEntity(messageType.getCode(), new Date());
        try {
            outgoingMessageService.save(entity, xml);
        } catch (SaveOutgoingMessageException e) {
            throw new ProcessIncomingMessageException("Can not proceed processing incoming-message.", e);
        }
    }
// ...
}

As i said If any exception occurred in steps 1-3, i want insert an error-record:

catch (Exception e) {
    if (e instanceof ProcessIncomingMessageException) {
        throw (ProcessIncomingMessageException) e;
    }
    e.printStackTrace();
    //send error outgoing-message
    OutgoingXmlModel outgoingXmlModel = new OutgoingXmlModel(refrenceId,ProcessResultStateEnum.FAILED.getCode(), e.getMessage());
    saveOutgoingMessage(outgoingXmlModel, registrationMessageType);
    return;
}

It’s SqlCommandHandlerServiceImpl.persist() method:

public class SqlCommandHandlerServiceImpl implements SqlCommandHandlerService {
// ...
    @Override
    @Transactional
    public void persist(IncomingXmlModel incomingXmlModel) {
        Collections.sort(incomingXmlModel.getTables());
        List<ParametricQuery> queries = generateSqlQueries(incomingXmlModel.getTables());
        for (ParametricQuery query : queries) {
            queryExecuter.executeQuery(query);
        }
    }
// ...
}

But when sqlCommandHandlerService.persist() throws exception (here a org.hibernate.exception.ConstraintViolationException exception), after inserting an error-record in OutgoingMessage table, when the transaction want to be committed , i get UnexpectedRollbackException. I can’t figure out where is my problem:

Exception in thread "null#0" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:717)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:394)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
    at ir.tamin.branch.insuranceregistration.services.schedular.ReceiveMessagesJob$$EnhancerByCGLIB$$63524c6b.run(<generated>)
    at ir.asta.wise.core.util.timer.JobScheduler$ScheduledJobThread.run(JobScheduler.java:132)

I’m using hibernate-4.1.0-Final, My database is oracle, and Here is my transaction-manager bean:

<bean id="transactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager"
    proxy-target-class="true" />

Транзакция помечена только как откат: как найти причину



у меня возникли проблемы с фиксацией транзакции в моем @Transactional методе:

methodA() {
methodB()
}

@Transactional
methodB() {
...
em.persist();
...
em.flush();
log("OK");
}

когда я вызываю methodB () из methodA (), метод успешно проходит, и я вижу «ОК» в своих журналах. Но тогда я получаю

Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
at methodA()...

  1. контекст methodB полностью отсутствует в исключении — что нормально, я полагаю?
  2. что-то в methodB() помечено транзакцией только как откат? Как я могу это узнать? Есть например способ проверить что-то вроде getCurrentTransaction().isRollbackOnly()? — как это я мог бы пройти через метод и найти причину.


3177  


7  

7 ответов:

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

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)

Я, наконец, понял проблему:

methodA() {
    methodB()
}

@Transactional(noRollbackFor = Exception.class)
methodB() {
    ...
    try {
        methodC()
    } catch (...) {...}
    log("OK");
}

@Transactional
methodC() {
    throw new ...();
}

что происходит, что хотя methodB имеет право аннотации methodC нет. Когда исключение выбрасывается, второй @Transactional первый транзакции, отката в любом случае.

чтобы быстро получить вызывающее исключение без необходимости перекодировать или перестроить установите точку останова на

org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3

и поднимайтесь в стек, обычно к какому-нибудь перехватчику. Там вы можете прочитать вызывающее исключение из некоторого блока catch.

Я боролся с этим исключением во время работы моего приложения.

наконец-то проблема была на SQL-запрос. я имею в виду, что запрос неверен.

пожалуйста, проверьте ваш запрос. Это мое предложение

ищите исключения, которые были брошены и пойманы в ... разделы вашего кода. Во время выполнения и для отката на предыдущую версию исключений приложения вызвать откат, когда выгоняют из бизнес-метод, даже если поймали на каком-то другом месте.

вы можете использовать контекст, чтобы узнать, помечена ли транзакция для отката.

@Resource
private SessionContext context;

context.getRollbackOnly();

отключите transactionmanager в вашем компоненте.xml

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

закомментируйте эти строки, и вы увидите исключение, вызывающее откат 😉

нашел хорошее объяснение с решениями: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) Удалите @Transacional из вложенного метода, если он действительно не требует управления транзакциями. Так что даже у него есть исключение, он просто пузырится и не влияет на транзакционные вещи.

или:

2) Если вложенный метод требует управления транзакциями, сделайте его как REQUIRE_NEW для политики распространения, которая способ даже если выбрасывает исключение и помечается только как откат, вызывающий объект не будет затронут.

Home
>
Java
>
Detail page

In spring transaction management, many people will encounter such an exception:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only   
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:718)   
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475)   
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)   
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)   
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)   
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)   

The scene of the above problem is similar to the following code:

ITestAService:

package com.gigamore.platform.ac.service;  
import com.onlyou.framework.exception.BusinessException;  
public interface ITestAService {      
    void testA() throws BusinessException;  
}  

TestAService:

package com.gigamore.platform.ac.service;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import org.springframework.transaction.annotation.Transactional;  
  
import com.gigamore.platform.base.service.impl.BaseServiceImpl;  
import com.onlyou.framework.exception.BusinessException;  
@Service  
public class TestAService extends BaseServiceImpl implements ITestAService{  
    @Autowired  
    private TestBService testBService;  
    @Transactional  
    public void testA(){  
        try{  
            testBService.testB();  
        }catch(BusinessException e){  
            logger.info(e.getMessage());  
        }catch(Exception e){  
            logger.info(e.getMessage());  
        }  
    }  
}  

TestBService:

package com.gigamore.platform.ac.service;  
  
import java.util.Date;  
  
import org.springframework.stereotype.Service;  
import org.springframework.transaction.annotation.Propagation;  
import org.springframework.transaction.annotation.Transactional;  
  
import com.gigamore.platform.ac.entity.LoanProjectEntity;  
import com.gigamore.platform.base.service.impl.BaseServiceImpl;  
import com.onlyou.framework.exception.BusinessException;  
@Service  
public class TestBService extends BaseServiceImpl{  
    @Transactional  
    public void testB(){  
        LoanProjectEntity project = this.selectByPrimaryKey(LoanProjectEntity.class, "2c9483e748321d4601485e1714d31412");  
        project.setUpdDataTm(new Date());  
        this.update(project);  
        throw new BusinessException(" Throwing anomaly ");  
    }  
}  

Test case:

@Autowired  
    private ITestAService testAService;  
    @Test  
    public void testA() {  
        testAService.testA();  
    }  

TestAService calls the testB() method of testBService, and throws a BusinessException exception in the testB() method, but testAService catches the exception with the try{}catch{} and does not throw it to the upper level.

It doesn’t seem to be a problem. the exception has been caught. In fact, when the testAService calls testBService testB() method, will go through a spring transaction control section, transaction itself will catch the exception from testB() method of testBService: TransactionAspectSupport.invokeWithinTransaction

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {  
            // Standard transaction demarcation with getTransaction and commit/rollback calls.  
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);  
            Object retVal = null;  
            try {  
                // This is an around advice: Invoke the next interceptor in the chain.  
                // This will normally result in a target object being invoked.  
                retVal = invocation.proceedWithInvocation();  
            }  
            catch (Throwable ex) {  
                // target invocation exception  
                completeTransactionAfterThrowing(txInfo, ex);  
                throw ex;  
            }  
            finally {  
                cleanupTransactionInfo(txInfo);  
            }  
            commitTransactionAfterReturning(txInfo);  
            return retVal;  
        }  

«completeTransactionAfterThrowing(txInfo, ex)» called «txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus())», the transaction manager rollback, the transaction is set to rollback-only.

The solution:

change the transaction annotation of the testB () method of TestBService to

@Transactional(propagation = Propagation.NESTED)

and indeed achieve the effect of avoiding exceptions.

Posted by seakwen
in Java
at Jul 26, 2017 — 9:57 AM
Tag:
Spring

Rollback по умолчанию

Предположим, что у нас есть сервис, который создает трех пользователей в рамках одной транзакции. Если что-то идет не так, выбрасывается java.lang.Exception.

@Service
public class PersonService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional
  public void addPeople(String name) throws Exception {
    personRepository.saveAndFlush(new Person("Jack", "Brown"));
    personRepository.saveAndFlush(new Person("Julia", "Green"));
    if (name == null) {
      throw new Exception("name cannot be null");
    }
    personRepository.saveAndFlush(new Person(name, "Purple"));
  }
}

А вот простой unit-тест.

@SpringBootTest
@AutoConfigureTestDatabase
class PersonServiceTest {
  @Autowired
  private PersonService personService;
  @Autowired
  private PersonRepository personRepository;

  @BeforeEach
  void beforeEach() {
    personRepository.deleteAll();
  }

  @Test
  void shouldRollbackTransactionIfNameIsNull() {
    assertThrows(Exception.class, () -> personService.addPeople(null));
    assertEquals(0, personRepository.count());
  }
}

Как думаете, тест завершится успешно, или нет? Логика говорит нам, что Spring должен откатить транзакцию из-за исключения. Следовательно personRepository.count() должен вернуть 0, так ведь? Не совсем.

expected: <0> but was: <2>
Expected :0
Actual   :2

Здесь требуются некоторые объяснения. По умолчанию Spring откатывает транзакции только в случае непроверяемого исключения. Проверяемые же считаются «восстанавливаемыми» из-за чего Spring вместо rollback делает commit. Поэтому personRepository.count() возращает 2.

Самый простой исправить это — заменить Exception на непроверяемое исключение. Например, NullPointerException. Либо можно переопределить атрибут rollbackFor у аннотации.

Например, оба этих метода корректно откатывают транзакцию.

@Service
public class PersonService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional(rollbackFor = Exception.class)
  public void addPeopleWithCheckedException(String name) throws Exception {
    addPeople(name, Exception::new);
  }

  @Transactional
  public void addPeopleWithNullPointerException(String name) {
    addPeople(name, NullPointerException::new);
  }

  private <T extends Exception> void addPeople(String name, Supplier<? extends T> exceptionSupplier) throws T {
    personRepository.saveAndFlush(new Person("Jack", "Brown"));
    personRepository.saveAndFlush(new Person("Julia", "Green"));
    if (name == null) {
      throw exceptionSupplier.get();
    }
    personRepository.saveAndFlush(new Person(name, "Purple"));
  }
}
@SpringBootTest
@AutoConfigureTestDatabase
class PersonServiceTest {
  @Autowired
  private PersonService personService;
  @Autowired
  private PersonRepository personRepository;

  @BeforeEach
  void beforeEach() {
    personRepository.deleteAll();
  }

  @Test
  void testThrowsExceptionAndRollback() {
    assertThrows(Exception.class, () -> personService.addPeopleWithCheckedException(null));
    assertEquals(0, personRepository.count());
  }

  @Test
  void testThrowsNullPointerExceptionAndRollback() {
    assertThrows(NullPointerException.class, () -> personService.addPeopleWithNullPointerException(null));
    assertEquals(0, personRepository.count());
  }

}

Rollback при «глушении» исключения

Не все исключения должны быть проброшены вверх по стеку вызовов. Иногда вполне допустимо отловить его внутри метода и залогировать информацию об этом.

Предположим, что у нас есть еще один транзакционный сервис, который проверяет, может ли быть создан пользователь с переданным именем. Если нет, выбрасывается IllegalArgumentException.

@Service
public class PersonValidateService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional
  public void validateName(String name) {
    if (name == null || name.isBlank() || personRepository.existsByFirstName(name)) {
      throw new IllegalArgumentException("name is forbidden");
    }
  }
}

Давайте добавим валидацию в наш PersonService.

@Service
@Slf4j
public class PersonService {
  @Autowired
  private PersonRepository personRepository;
  @Autowired
  private PersonValidateService personValidateService;

  @Transactional
  public void addPeople(String name) {
    personRepository.saveAndFlush(new Person("Jack", "Brown"));
    personRepository.saveAndFlush(new Person("Julia", "Green"));
    String resultName = name;
    try {
      personValidateService.validateName(name);
    }
    catch (IllegalArgumentException e) {
      log.error("name is not allowed. Using default one");
      resultName = "DefaultName";
    }
    personRepository.saveAndFlush(new Person(resultName, "Purple"));
  }
}

Если валидация не проходит, создаем пользователя с именем по умолчанию.

Окей, теперь нужно протестировать новую функциональность.

@SpringBootTest
@AutoConfigureTestDatabase
class PersonServiceTest {
  @Autowired
  private PersonService personService;
  @Autowired
  private PersonRepository personRepository;

  @BeforeEach
  void beforeEach() {
    personRepository.deleteAll();
  }

  @Test
  void shouldCreatePersonWithDefaultName() {
    assertDoesNotThrow(() -> personService.addPeople(null));
    Optional<Person> defaultPerson = personRepository.findByFirstName("DefaultName");
    assertTrue(defaultPerson.isPresent());
  }
}

Однако результат оказывается довольно неожиданным.

Unexpected exception thrown:
org.springframework.transaction.UnexpectedRollbackException:
Transaction silently rolled back because it has been marked as rollback-only

Странно. Мы отловили исключение. Почему же Spring откатил транзакцию? Прежде всего нужно разобраться с тем, как Spring работает с транзакциями.

Под капотом Spring применяет паттерн аспектно-ориентированного программирования. Опуская сложные детали, идея заключается в том, что bean оборачивается в прокси, который генерируются в процессе старта приложения. Внутри этого прокси выполняется требуемая логика. В нашем случае, управление транзакциями. Когда какой-нибудь bean указывает транзакционный сервис в качестве DI зависимости, Spring на самом деле внедряет прокси.

Ниже представлен workflow вызова вышенаписанного метода addPeople.

Параметр propagation у @Transactional по умолчанию имеет значение REQUIRED. Это значит, что новая транзакция создается, если она отсутствует. Иначе выполнение продолжается в текущей. Так что в нашем случае весь запрос выполняется в рамках единственной транзакции.

Однако здесь есть нюанс. Если RuntimeException был выброшен из-за границ transactional proxy, то Spring отмечает текущую транзакцию как rollback-only. Здесь у нас именно такой случай. PersonValidateService.validateName выбросил IllegalArgumentException. Transactional proxy выставил флаг rollback. Дальнейшие операции уже не имеют значения, так как в конечном итоге транзакция не закоммитится.

Каково решение проблемы? Вообще их несколько. Например, мы можем добавить атрибут noRollbackFor в PersonValidateService.

@Service
public class PersonValidateService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional(noRollbackFor = IllegalArgumentException.class)
  public void validateName(String name) {
    if (name == null || name.isBlank() || personRepository.existsByFirstName(name)) {
      throw new IllegalArgumentException("name is forbidden");
    }
  }
}

Есть вариант поменять propagation на REQUIRES_NEW. В этом случае PersonValidateService.validateName будет выполнен в отдельной транзакции. Так что родительская не будет отменена.

@Service
public class PersonValidateService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void validateName(String name) {
    if (name == null || name.isBlank() || personRepository.existsByFirstName(name)) {
      throw new IllegalArgumentException("name is forbidden");
    }
  }
}

Возможные проблемы с Kotlin

У Kotlin много схожестей с Java. Но управление исключениями не является одной из них.

Kotlin убрал понятия проверяемых и непроверяемых исключений. Строго говоря, все исключения в Kotlin непроверяемые, потому что нам не требуется указывать конструкции throws SomeException в сигнатурах методов, а также оборачивать их вызовы в try-catch. Обсуждение плюсов и минусов такого решения — тема для отдельной статьи. Но сейчас я хочу продемонстрировать вам проблемы, которые могут из-за этого возникнуть при использовании Spring Data.

Давайте перепишем самый первый пример с java.lang.Exception на Kotlin.

@Service
class PersonService(
    @Autowired
    private val personRepository: PersonRepository
) {
    @Transactional
    fun addPeople(name: String?) {
        personRepository.saveAndFlush(Person("Jack", "Brown"))
        personRepository.saveAndFlush(Person("Julia", "Green"))
        if (name == null) {
            throw Exception("name cannot be null")
        }
        personRepository.saveAndFlush(Person(name, "Purple"))
    }
}
@SpringBootTest
@AutoConfigureTestDatabase
internal class PersonServiceTest {
    @Autowired
    lateinit var personRepository: PersonRepository

    @Autowired
    lateinit var personService: PersonService

    @BeforeEach
    fun beforeEach() {
        personRepository.deleteAll()
    }

    @Test
    fun `should rollback transaction if name is null`() {
        assertThrows(Exception::class.java) { personService.addPeople(null) }
        assertEquals(0, personRepository.count())
    }
}

Тест падает как в Java.

expected: <0> but was: <2>
Expected :0
Actual   :2

Здесь нет ничего удивительного. Spring управляет транзакциями в Kotlin ровно так же, как и в Java. Но в Java мы не можем вызвать метод, который выбрасывает java.lang.Exception, не оборачивая инструкцию в try-catch или не пробрасывая исключение дальше. Kotlin же позволяет. Это может привести к непредвиденным ошибкам и трудно уловимым багам. Так что к таким вещам следует относиться вдвойне внимательнее.

Строго говоря, в Java есть хак, который позволяет выбросить checked исключения, не указывая throws в сигнатуре.

Заключение

Это все, что я хотел рассказать о @Transactionalв Spring. Если у вас есть какие-то вопросы или пожелания, пожалуйста, оставляйте комментарии. Спасибо за чтение!

Spring — самый популярный фреймворк в мире Java. Разработчикам из «коробки» доступны инструменты для API, ролевой модели, кэширования и доступа к данным. Spring Data в особенности делает жизнь программиста гораздо легче. Нам больше не нужно беспокоиться о соединениях к базе данных и управлении транзакциями. Фреймворк сделает все за нас. Однако тот факт, что многие детали остаются для нас скрытыми, может привести к трудно отлавливаемым багам и ошибкам. Так что давайте глубже погрузимся в аннотацию @Transaсtional и узнаем, что же там происходит.

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Ошибка этот номер нельзя использовать для подтверждения id
  • Ошибка сони плейстейшен su 30746 0