@Transactional
l'annotation au printemps fonctionne en enveloppant votre objet dans un proxy qui à son tour enveloppe les méthodes annotées avec @Transactional
dans une opération. En raison de cette annotation ne fonctionnera pas sur les méthodes privées (comme dans votre exemple) car les méthodes privées ne peuvent pas être héritées => ils ne peuvent pas être enveloppés (ce n'est pas vrai si vous utilisez des transactions déclaratives avec aspectj, alors les mises en garde liées au proxy ci-dessous ne s'appliquent pas).
Voici une explication de base de la façon dont @Transactional
la magie du printemps opère.
Vous avez écrit :
class A {
@Transactional
public void method() {
}
}
Mais voici ce que vous obtenez réellement lorsque vous injectez un haricot :
class ProxiedA extends A {
private final A a;
public ProxiedA(A a) {
this.a = a;
}
@Override
public void method() {
try {
// open transaction ...
a.method();
// commit transaction
} catch (RuntimeException e) {
// rollback transaction
} catch (Exception e) {
// commit transaction
}
}
}
Cela a des limites. Ils ne fonctionnent pas avec @PostConstruct
méthodes car elles sont appelées avant que l'objet ne soit proxy. Et même si vous avez tout configuré correctement, les transactions ne sont annulées que sur non cochées exception par défaut. Utilisez @Transactional(rollbackFor={CustomCheckedException.class})
si vous avez besoin d'une restauration sur une exception vérifiée.
Une autre mise en garde fréquemment rencontrée que je connais :
@Transactional
la méthode ne fonctionnera que si vous l'appelez "de l'extérieur", dans l'exemple suivant b()
ne sera pas enveloppé dans la transaction :
class X {
public void a() {
b();
}
@Transactional
public void b() {
}
}
C'est aussi parce que @Transactional
fonctionne en proxyant votre objet. Dans l'exemple ci-dessus a()
appellera X.b()
n'est pas une méthode "printemps proxy" améliorée b()
il n'y aura donc pas de transaction. Comme solution de contournement, vous devez appeler b()
d'un autre bean.
Lorsque vous rencontrez l'une de ces mises en garde et que vous ne pouvez pas utiliser une solution de contournement suggérée (rendez la méthode non privée ou appelez b()
d'un autre bean), vous pouvez utiliser TransactionTemplate
au lieu de transactions déclaratives :
public class A {
@Autowired
TransactionTemplate transactionTemplate;
public void method() {
transactionTemplate.execute(status -> {
A();
B();
return null;
});
}
...
}
Mettre à jour
Répondre à la question mise à jour de l'OP en utilisant les informations ci-dessus.
Quelle méthode doit être annotée avec @Transactional:changes() ? base de donnéesChanges() ?
@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Assurez-vous que changes()
est appelé "de l'extérieur" d'un bean, pas de la classe elle-même et après l'instanciation du contexte (par exemple, ce n'est pas afterPropertiesSet()
ou @PostConstruct
méthode annotée). Comprenez que le printemps annule la transaction uniquement pour les exceptions non vérifiées par défaut (essayez d'être plus précis dans la liste des exceptions vérifiées rollbackFor).