Anotación @Transactional. ¿Cómo revertir?

91

Usé esta anotación con éxito para una clase de Dao. Y la reversión funciona para las pruebas.

Pero ahora necesito revertir el código real, no solo las pruebas. Hay anotaciones especiales para usar en pruebas. Pero, ¿qué anotaciones son para código que no es de prueba? Es una gran pregunta para mí. Ya pasé un día para eso. La documentación oficial no satisfizo mis necesidades.

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

employeeDao es

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

Y aquí hay una prueba para la que las anotaciones funcionan bien:

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



** SÍ, revoqué la transacción. Acabo de agregar BEAN para el servicio ... y luego la anotación @Transactional comienza a funcionar :-) **

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

¡Gracias a todos, Rusia no los olvidará!

Chico de Moscú
fuente

Respuestas:

163

Simplemente arroje cualquiera RuntimeExceptionde un método marcado como @Transactional.

De forma predeterminada, todas RuntimeExceptionlas transacciones de reversión, mientras que las excepciones marcadas no lo hacen. Este es un legado de EJB. Esto se puede configurar mediante el uso rollbackFor()y noRollbackFor()parámetros de anotación:

@Transactional(rollbackFor=Exception.class)

Esto revertirá la transacción después de lanzar cualquier excepción.

Tomasz Nurkiewicz
fuente
1
Lo intenté. ¡No funciona! Hablo de la operación Rollback no directamente en el objeto Dao, que puede funcionar. Hablo de otro objeto que usa la secuencia de llamada del método Dao que usa @Transactional. Y trato de agregar la misma anotación para mi clase que llama a este Dao. Pero aquí no funciona.
Moscow Boy
5
Realmente funciona de esta manera :-). Si tiene un servicio que llama a varios DAO, el servicio también debe tener una @Transactionalanotación. De lo contrario, cada llamada DAO comienza y confirma una nueva transacción antes de lanzar una excepción en el servicio. La conclusión es: la excepción debe dejar (escapar) un método marcado como @Transactional.
Tomasz Nurkiewicz
Mira el código agregado en la primera publicación. Hay parte del código que tengo. Y después de la ejecución tengo el registro en DB. => la reversión aún no funciona.
Moscow Boy
¿Lanzar una excepción de DAO no revierte la transacción también? ¿Ha org.springframework.orm.hibernate3.HibernateTransactionManagerconfigurado en su contexto de primavera? Si habilita el org.springframework.transactionregistrador, ¿muestra algo interesante?
Tomasz Nurkiewicz
1
Si está usando Spring Boot y deja que configure automáticamente el DataSource, usará HikariDataSource que tiene el auto-commit establecido en verdadero por defecto. Debe establecerlo en falso: hikariDataSource.setAutoCommit (false); Hice este cambio en la clase @Configuration al configurar el bean DataSourceTransactionManager.
Alex
83

o programáticamente

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Stefan K.
fuente
12
Si. Porque a veces necesitas revertir sin arrojar un error.
Erica Kane
2
Eres un salvavidas.
Walls
Esta es una forma más apropiada de invocar un imo de reversión, idealmente no desea lanzar excepciones para condiciones conocidas o controladas.
jalpino
2
A veces no funciona. Por ejemplo, en mi caso tengo un servicio transaccional y un repositorio no transaccional. Llamo al repositorio desde el servicio, por lo que debería participar en una transacción abierta por el servicio. Pero si llamo a su código desde mi repositorio no transaccional, arroja NoTransactionException a pesar de que la transacción existe.
Victor Dombrovsky
3
¿Qué transacción desea revertir en su "repositorio no transaccional"?
Stefan K.
5

Puede lanzar una excepción sin marcar del método que desea revertir. Esto se detectará en primavera y su transacción se marcará solo como reversión.

Supongo que estás usando Spring aquí. Y supongo que las anotaciones a las que hace referencia en sus pruebas son las anotaciones basadas en pruebas de primavera.

La forma recomendada de indicar a la infraestructura de transacciones de Spring Framework que el trabajo de una transacción debe revertirse es lanzar una excepción del código que se está ejecutando actualmente en el contexto de una transacción.

y tenga en cuenta que:

tenga en cuenta que el código de infraestructura de transacciones de Spring Framework, de forma predeterminada, solo marcará una transacción para reversión en el caso de tiempo de ejecución, excepciones no marcadas; es decir, cuando la excepción lanzada es una instancia o subclase de RuntimeException.

Alex Barnes
fuente
1

Para mí rollbackFor no fue suficiente, así que tuve que poner esto y funciona como se esperaba:

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

Espero que ayude :-)

Roberto rodriguez
fuente
1
no, no ayuda en absoluto a TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();continuación, ayudaste
Enerccio