Anotação @Transactional. Como reverter?

91

Usei essa anotação com sucesso para uma classe Dao. E a reversão funciona para testes.

Mas agora preciso reverter o código real, não apenas os testes. Existem anotações especiais para uso em testes. Mas quais anotações são para código que não é de teste? É uma grande questão para mim. Já passei um dia por isso. A documentação oficial não atendeu às minhas necessidades.

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

empregadoDao é

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

E aqui está um teste para o qual as anotações funcionam bem:

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

HibernateDaoBeans.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>
   ...



** SIM, eu reverti a transação. Acabei de adicionar BEAN para o serviço ... e então a anotação @Transactional begin to work :-) **

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

Obrigado a todos, a Rússia não vai esquecer de vocês!

Menino moscou
fonte

Respostas:

160

Basta jogar qualquer RuntimeExceptionum de um método marcado como @Transactional.

Por padrão, todas RuntimeExceptionas transações de rollback, enquanto as exceções verificadas não. Este é um legado EJB. Você pode configurar isso usando os parâmetros de anotação rollbackFor()e noRollbackFor():

@Transactional(rollbackFor=Exception.class)

Isso irá reverter a transação após lançar qualquer exceção.

Tomasz Nurkiewicz
fonte
1
Eu tentei. Não funciona! Eu falo sobre a operação de Rollback não diretamente no objeto Dao, que pode funcionar. Falo sobre outro objeto que usa seqüência de chamada de Dao methoda que usa @Transactional. E tento adicionar a mesma anotação para minha classe que chama isso de Dao. Mas não funciona aqui.
Moscow Boy
5
Realmente funciona assim :-). Se você tiver um serviço chamando vários DAOs, o serviço também precisa ter uma @Transactionalanotação. Caso contrário, cada chamada DAO começa e confirma uma nova transação antes de lançar uma exceção no serviço. O resultado final é: a exceção deve deixar (escapar) um método marcado como @Transactional.
Tomasz Nurkiewicz
Veja o código adicionado na primeira postagem. Eu tenho parte do código. E após a execução tenho o registro no DB. => rollback ainda não funciona.
Moscow Boy
Lançar uma exceção do DAO não reverte a transação também? Você org.springframework.orm.hibernate3.HibernateTransactionManagerconfigurou em seu contexto Spring? Se você ativar o org.springframework.transactionlogger, ele mostra algo interessante?
Tomasz Nurkiewicz
1
Se você estiver usando Spring Boot e deixá-lo configurar automaticamente o DataSource, ele usará HikariDataSource, que tem o auto-commit definido como verdadeiro por padrão. Você precisa defini-lo como falso: hikariDataSource.setAutoCommit (false); Fiz essa alteração na classe @Configuration ao configurar o bean DataSourceTransactionManager.
Alex
82

ou programaticamente

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Stefan K.
fonte
12
Sim. Porque às vezes você precisa reverter sem gerar um erro.
Erica Kane de
2
Você é um salva-vidas.
Paredes de
Esta é uma maneira mais apropriada de invocar um rollback imo, idealmente você não deseja lançar exceções para condições conhecidas ou controladas.
jalpino de
2
Às vezes não funciona. Por exemplo, no meu caso, tenho um serviço transacional e um repositório não transacional. Eu chamo o repositório do serviço, então, ele deve participar de uma transação aberta pelo serviço. Mas se eu chamar seu código do meu repositório não transacional, ele lançará NoTransactionException, apesar do fato de que a transação existe.
Victor Dombrovsky
3
qual transação você deseja reverter em seu "repositório não transacional"?
Stefan K.
5

Você pode lançar uma exceção não verificada do método que deseja reverter. Isso será detectado na primavera e sua transação será marcada apenas como reversão.

Presumo que você esteja usando Spring aqui. E suponho que as anotações às quais você se refere em seus testes são as anotações baseadas no teste de mola.

A maneira recomendada de indicar à infraestrutura de transação do Spring Framework que o trabalho de uma transação deve ser revertido é lançar uma Exceção do código que está em execução no contexto de uma transação.

e observe que:

observe que o código de infraestrutura de transação do Spring Framework irá, por padrão, apenas marcar uma transação para reversão no caso de tempo de execução, exceções não verificadas; ou seja, quando a exceção lançada é uma instância ou subclasse de RuntimeException.

Alex Barnes
fonte
1

Para mim o rollbackFor não foi suficiente, então tive que colocar isso e funcionou conforme o esperado:

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

Espero que ajude :-)

Roberto Rodriguez
fonte
1
não, não ajuda em nada TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();abaixo de você ajudou
Enerccio