Annotation @Transactional. Comment revenir en arrière?

91

J'ai utilisé cette annotation avec succès pour une classe Dao. Et la restauration fonctionne pour les tests.

Mais maintenant, j'ai besoin de restaurer du vrai code, pas seulement des tests. Il existe des annotations spéciales à utiliser dans les tests. Mais quelles annotations sont pour le code non test? C'est une grande question pour moi. J'ai déjà passé une journée pour ça. La documentation officielle ne répondait pas à mes besoins.

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

employéDao est

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

Et voici un test pour lequel les annotations fonctionnent 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>
   ...



** OUI, j'ai annulé la transaction. Je viens d'ajouter BEAN pour le service ... puis l'annotation @Transactional commence à fonctionner :-) **

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

Merci à tous, la Russie ne vous oubliera pas!

Garçon de Moscou
la source

Réponses:

160

Jetez-en simplement une à RuntimeExceptionpartir d'une méthode marquée comme @Transactional.

Par défaut, toutes RuntimeExceptionles transactions d'annulation, contrairement aux exceptions cochées. C'est un héritage EJB. Vous pouvez configurer cela à l'aide des paramètres d'annotation rollbackFor()et noRollbackFor():

@Transactional(rollbackFor=Exception.class)

Cela annulera la transaction après avoir levé une exception.

Tomasz Nurkiewicz
la source
1
Je l'ai essayé. Ça ne marche pas! Je parle de l'opération de restauration pas directement dans l'objet Dao, cela fonctionnera peut-être. Je parle d'un autre objet qui utilise une séquence d'appel de la méthode Dao qui utilise @Transactional. Et j'essaye d'ajouter la même annotation pour ma classe qui appelle cela Dao. Mais ça ne marche pas ici.
Moscou Boy
5
Cela fonctionne vraiment de cette façon :-). Si vous avez un service appelant plusieurs DAO, le service doit également avoir une @Transactionalannotation. Sinon, chaque appel DAO démarre et valide une nouvelle transaction avant de lever une exception dans le service. La ligne du bas est: l'exception doit laisser (échapper) une méthode marquée comme @Transactional.
Tomasz Nurkiewicz
Regardez le code ajouté dans le premier message. Il y a une partie du code que j'ai. Et après l'exécution, j'ai le dossier dans DB. => la restauration ne fonctionne pas encore.
Moscow Boy
Est-ce que lever une exception de DAO n'annule pas non plus la transaction? Avez-vous org.springframework.orm.hibernate3.HibernateTransactionManagerconfiguré dans votre contexte Spring? Si vous activez l' org.springframework.transactionenregistreur, cela montre-t-il quelque chose d'intéressant?
Tomasz Nurkiewicz
1
Si vous utilisez Spring Boot et laissez-le configurer automatiquement le DataSource, il utilisera HikariDataSource dont l'auto-commit est défini sur true par défaut. Vous devez le définir sur false: hikariDataSource.setAutoCommit (false); J'ai effectué cette modification dans la classe @Configuration lors de la configuration du bean DataSourceTransactionManager.
Alex le
82

ou par programme

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Stefan K.
la source
12
Oui. Parce que parfois, vous devez revenir en arrière sans générer d'erreur.
Erica Kane
2
Tu es un sauveur.
Murs
C'est une manière plus appropriée d'appeler une imo de restauration, idéalement, vous ne voulez pas lever d'exceptions pour des conditions connues ou contrôlées.
jalpino le
2
Parfois ça ne marche pas. Par exemple, dans mon cas, j'ai un service transactionnel et un référentiel non transactionnel. J'appelle le référentiel du service, donc, il devrait participer à une transaction ouverte par le service. Mais si j'appelle votre code à partir de mon référentiel non transactionnel, il lance NoTransactionException malgré le fait que la transaction existe.
Victor Dombrovsky
3
quelle transaction souhaitez-vous annuler dans votre "référentiel non transactionnel"?
Stefan K.
5

Vous pouvez lever une exception non vérifiée à partir de la méthode que vous souhaitez restaurer. Cela sera détecté au printemps et votre transaction sera marquée comme annulation uniquement.

Je suppose que vous utilisez Spring ici. Et je suppose que les annotations auxquelles vous faites référence dans vos tests sont les annotations basées sur les tests de ressorts.

La méthode recommandée pour indiquer à l'infrastructure de transaction de Spring Framework que le travail d'une transaction doit être annulé consiste à lever une exception à partir du code en cours d'exécution dans le contexte d'une transaction.

et notez que:

veuillez noter que le code d'infrastructure de transaction de Spring Framework marquera, par défaut, une transaction pour une annulation uniquement dans le cas d'exceptions non vérifiées à l'exécution; autrement dit, lorsque l'exception levée est une instance ou une sous-classe de RuntimeException.

Alex Barnes
la source
1

Pour moi, rollbackFor n'était pas suffisant, j'ai donc dû mettre ceci et cela fonctionne comme prévu:

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

J'espère que cela aide :-)

Roberto Rodriguez
la source
1
non ça n'aide pas du tout TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();ci-dessous vous avez aidé
Enerccio