Adnotacja @Transactional. Jak wycofać?

91

Z powodzeniem użyłem tej adnotacji na zajęciach Dao. Przywracanie działa również w przypadku testów.

Ale teraz potrzebuję przywrócić rzeczywisty kod, a nie tylko testy. Istnieją specjalne adnotacje do wykorzystania w testach. Ale które adnotacje dotyczą kodu niebędącego testem? To dla mnie duże pytanie. Spędziłem już na to dzień. Oficjalna dokumentacja nie spełniała moich potrzeb.

class MyClass { // this does not make rollback! And record appears in DB.
        EmployeeDaoInterface employeeDao;

        public MyClass() {
            ApplicationContext context = new ClassPathXmlApplicationContext(
                    new String[] { "HibernateDaoBeans.xml" });
            employeeDao = (IEmployeeDao) context.getBean("employeeDao");
         }

        @Transactional(rollbackFor={Exception.class})
    public void doInsert( Employee newEmp ) throws Exception {
        employeeDao.insertEmployee(newEmp);
        throw new RuntimeException();
    }
}

Pracownik jest

@Transactional
public class EmployeeDao implements IEmployeeDao {
    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public void insertEmployee(Employee emp) {
        sessionFactory.getCurrentSession().save(emp);
    }
}

A oto test, w przypadku którego adnotacje działają dobrze:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/HibernateDaoBeans.xml" })
@TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
@Transactional
public class EmployeeDaoTest {

    @Autowired
    EmployeeDaoInterface empDao;

    @Test
    public void insert_record() {
       ...
       assertTrue(empDao.insertEmployee(newEmp));
    }

HibernateBIZBeans.xml

   ...
<bean id="employeeDao" class="Hibernate.EmployeeDao">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
    <tx:annotation-driven transaction-manager="txManager"/>

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



** TAK, wycofałem transakcję. Właśnie dodałem BEAN do usługi ... a potem adnotacja @Transactional zaczyna działać :-) **

<bean id="service" class="main.MyClass">
    <property name="employeeDao" ref="employeeDao" />
</bean>

Dzięki wszystkim, Rosja cię nie zapomni!

Moscow Boy
źródło

Odpowiedzi:

163

Po prostu wyrzuć dowolną RuntimeExceptionmetodę oznaczoną jako @Transactional.

Domyślnie wszystkie RuntimeExceptiontransakcje są wycofywane, podczas gdy zaznaczone wyjątki nie. To jest dziedzictwo EJB. Można to skonfigurować za pomocą parametrów rollbackFor()i noRollbackFor()adnotacji:

@Transactional(rollbackFor=Exception.class)

Spowoduje to wycofanie transakcji po zgłoszeniu dowolnego wyjątku.

Tomasz Nurkiewicz
źródło
1
Próbowałem tego. To nie działa! Mówię o operacji wycofywania nie bezpośrednio w obiekcie Dao, to może zadziała. Opowiadam o innym obiekcie, który używa sekwencji wywołań metody Dao, który używa @Transactional. I staram się dodać tę samą adnotację dla mojej klasy, która nazywa to Dao. Ale to nie działa tutaj.
Moscow Boy
5
To naprawdę działa w ten sposób :-). Jeśli masz usługę wywołującą kilka DAO, usługa również musi mieć @Transactionaladnotację. W przeciwnym razie każde wywołanie DAO rozpoczyna się i zatwierdza nową transakcję, zanim zgłosi wyjątek w usłudze. Najważniejsze jest to: wyjątek musi opuścić (uciec) metodę oznaczoną jako @Transactional.
Tomasz Nurkiewicz
Spójrz na dodany kod w pierwszym poście. Mam część kodu. A po wykonaniu mam rekord w DB. => wycofywanie jeszcze nie działa.
Moscow Boy
Czy zgłoszenie wyjątku z DAO nie powoduje również wycofania transakcji? Czy org.springframework.orm.hibernate3.HibernateTransactionManagerskonfigurowałeś w kontekście Spring? Jeśli włączysz org.springframework.transactionrejestrator, czy pokazuje on coś interesującego?
Tomasz Nurkiewicz
1
Jeśli używasz Spring Boot i pozwolisz mu automatycznie skonfigurować DataSource, użyje HikariDataSource, który ma domyślnie ustawione automatyczne zatwierdzanie na true. Musisz ustawić ją na false: hikariDataSource.setAutoCommit (false); Dokonałem tej zmiany w klasie @Configuration podczas konfigurowania beana DataSourceTransactionManager.
Alex
83

lub programowo

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Stefan K.
źródło
12
Tak. Ponieważ czasami trzeba wycofać bez zgłaszania błędu.
Erica Kane
2
Jesteś ratownikiem.
Walls
Jest to bardziej odpowiedni sposób wywoływania wycofywania imo, najlepiej nie chcesz zgłaszać wyjątków dla znanych lub kontrolowanych warunków.
jalpino
2
Czasami to nie działa. Na przykład w moim przypadku mam usługę transakcyjną i repozytorium nietransakcyjne. Wywołuję repozytorium z serwisu, więc powinno uczestniczyć w transakcji otwieranej przez serwis. Ale jeśli wywołam twój kod z mojego nietransakcyjnego repozytorium, zgłosi NoTransactionException pomimo faktu, że transakcja istnieje.
Victor Dombrovsky
3
którą transakcję chcesz wycofać w swoim „nietransakcyjnym repozytorium”?
Stefan K.
5

Możesz zgłosić niezaznaczony wyjątek od metody, którą chcesz wycofać. Zostanie to wykryte do wiosny, a Twoja transakcja zostanie oznaczona jako tylko wycofanie.

Zakładam, że używasz tutaj Spring. Zakładam, że adnotacje, do których odwołujesz się w swoich testach, są adnotacjami opartymi na teście sprężynowym.

Zalecanym sposobem wskazania infrastrukturze transakcyjnej Spring Framework, że praca transakcji ma zostać wycofana, jest zgłoszenie wyjątku z kodu, który jest obecnie wykonywany w kontekście transakcji.

i zauważ, że:

należy pamiętać, że kod infrastruktury transakcji Spring Framework będzie domyślnie oznaczał transakcję do wycofania tylko w przypadku niezaznaczonych wyjątków w czasie wykonywania; to znaczy, gdy zgłoszony wyjątek jest wystąpieniem lub podklasą RuntimeException.

Alex Barnes
źródło
1

Dla mnie rollbackFor to za mało, więc musiałem to włożyć i działa zgodnie z oczekiwaniami:

@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)

Mam nadzieję, że to pomoże :-)

Roberto Rodriguez
źródło
1
nie, to wcale nie pomaga, TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();poniżej pomogłeś
Enerccio