spring transaction - rollback only na true w prywatnej metodzie

spring transaction - rollback only na true w prywatnej metodzie
  • Rejestracja: dni
  • Ostatnio: dni
0

Hej,
Podczas próby zapisu dostaję wyjątek rzucany przez org.springframework.orm.jpa.JpaSystemException dotyczący ze jest zaznaczony parametr rollback only. Wiem z czym jest błąd związany ale nie wiem jak mogę to obejść.
Błąd występuje dlatego, że metoda execute() rzuca wyjątkiem
Poniżej mój psełdokod:

Kopiuj
public class MyService {
    MyJpaRepository myJpaRepository;
    EntityManager entityManager;
  
        @Transactional
        @Override 
        public void doSomething(){

               MyObject myObject  = myJpaRepository.findOne(1L);               
               Try<Integer>result = updateChild(myObject.getchild());
               myObject .addResult(result .get());

                myJpaRepository.save(myObject );
  
      
        }

       Try<Integer>updateChildren(MyObjectChilden child) {
            String query = child.prepareQuery("xxx");
            Try<Integer> result = Try.of(() -> execute(query));

            if(result.isFailure()){
                  String query = child.prepareQuery("YYY");  //jak rzuci błąd unique constraint validation zmieniam na "YYY"
                  result = Try.of(() -> execute(query ));
             }
             return result
     }
   
    private Integer execute(String query) {
            final Query nativeQuery = entityManager.createNativeQuery(query); //Może rzucić błąd unique constraint validation  
            return (Integer) nativeQuery.getSingleResult();
    }
}

Ogólnie idea jest taka ze wydziełiłem execute() zeby móc ją wielokrotnie wykorzystać ale z innym query, niestety ta metoda rzuca wyjątek przez co zaznaczony jest parametr rollback only i nie ma możliwości zapisu do bazy

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
0

Dziwne. Wyjątek nie powinien być widziany przez Spring magię.
Coś chyba innego się dzieje.
Zrób w metodzie doSomething : entityManager.flush() opakuj w try catch i zobacz co się dzieje.

  • Rejestracja: dni
  • Ostatnio: dni
0

@Transactional(noRollbackFor={JpaSystemException.class})

D9
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 54
0

Po wywołaniu entityManager.flush() nic się nie dzieje, natomiast jak się kończy transakcja (metoda doSomething()) dostaję wyjątek: Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit - znacznik rollback only ustawiony jest na true podczas rzucenia wyjątku 'unique constraint' w metodzie 'execute(String query)'

@Transactional(noRollbackFor={JpaSystemException.class}) - po ustawieniu tego parametru też leci ten sam wyjątek

Problem rozwiązałem poprzez wyniesienie metody execute(String query) do osobnej transakcji

jarekczek
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Siemianowice Śląskie
  • Postów: 500
0

Wydaje mi się, że skupiasz się na złym komunikacie. Szukałbym pod spodem kolejnego stack trace'u z zawiniętym wyjątkiem (cause). Czasem takie piętra wyjątków mają wysokość 5 i najciekawsze jest na samym dole. Typowy błąd:

throw new RuntimeException(e.getMessage())

zamiast

throw new RuntimeException(e)

To drugie zachowuje cause.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
1

Wyjaśnione.

W skrócie rację miał @Koziołek, a ja nie wiedziałem jednej zabawnej rzeczy.
**Hibernate **faktycznie sam sobie pomarkuje transackcje jako rollback only, nawet jeśli obsłużymy Exception. Dotyczy to niektórych rodzajów exceptionów...nawet takich co nie widać w przetwarzaniu typu: StaleStateException (i dzieje się przy OptimisticLockException)

Elegancka metoda ze źródeł hibernate :-) ponizej:

Elegancja

Kopiuj
@Override
	public void handlePersistenceException(PersistenceException e) {
		if ( e instanceof NoResultException ) {
			return;
		}
		if ( e instanceof NonUniqueResultException ) {
			return;
		}
		if ( e instanceof LockTimeoutException ) {
			return;
		}
		if ( e instanceof QueryTimeoutException ) {
			return;
		}

		try {
			markForRollbackOnly();
		}
		catch ( Exception ne ) {
			//we do not want the subsequent exception to swallow the original one
			LOG.unableToMarkForRollbackOnPersistenceException(ne);
		}
}

Generalnie oznacza to też, że hibernate + spring nie daje szansy na ludzką obsługę błędów z użyciem Try. Jeśli złapiemy wyjątek i oddamy Try z failem to interceptor springowy z radością stwierdzi, że nie było błędu (nie poleciał exception) czyli trzeba zatwierdzić transakcję commitem.... co się nie uda, bo jest ustawiony rollbackonly i leci nowy exception. Nawet mam taki case na produkcji teraz, chociaz jedyny problem to smiecenie logów. Ale może być niebezpiecznie. Magia k...a.

jarekczek
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Siemianowice Śląskie
  • Postów: 500
0

Taki problem, jak pokazał OP, rozwiązuje się chyba wewnętrznymi transakcjiami. NESTED albo REQUIRES_NEW. Rollback jest na miejscu, ale nie chcemy go do głównej transakcji, tylko do wewnętrznej.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.