@Transactional
adnotacja na wiosnę działa poprzez owinięcie obiektu w proxy, który z kolei zawija metody z adnotacjami @Transactional
w transakcji. Z tego powodu adnotacja nie będzie działać w przypadku metod prywatnych (jak w Twoim przykładzie), ponieważ metody prywatne nie mogą być dziedziczone => nie mogą być opakowane (nie jest to prawdą, jeśli używasz transakcji deklaratywnych z aspektem, wówczas poniższe zastrzeżenia dotyczące proxy nie mają zastosowania).
Oto podstawowe wyjaśnienie, w jaki sposób @Transactional
wiosenna magia działa.
Napisałeś:
class A {
@Transactional
public void method() {
}
}
Ale to właśnie otrzymujesz po wstrzyknięciu fasoli:
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
}
}
}
To ma ograniczenia. Nie działają z @PostConstruct
metody, ponieważ są wywoływane przed serwerem proxy. A nawet jeśli wszystko skonfigurowałeś poprawnie, transakcje są cofane tylko po niezaznaczeniu wyjątki domyślnie. Użyj @Transactional(rollbackFor={CustomCheckedException.class})
jeśli potrzebujesz wycofania jakiegoś zaznaczonego wyjątku.
Inne często spotykane zastrzeżenie, które znam:
@Transactional
metoda zadziała tylko jeśli nazwiesz ją "z zewnątrz", w poniższym przykładzie b()
nie zostaną objęte transakcją:
class X {
public void a() {
b();
}
@Transactional
public void b() {
}
}
Dzieje się tak również dlatego, że @Transactional
działa poprzez proxy twojego obiektu. W powyższym przykładzie a()
wywoła X.b()
nie ulepszona metoda "wiosenne proxy" b()
więc nie będzie transakcji. Jako obejście musisz wywołać b()
z innego ziarna.
Gdy napotkasz którekolwiek z tych ostrzeżeń i nie możesz użyć sugerowanego obejścia (ustaw metodę jako nieprywatną lub wywołaj b()
z innego ziarna) możesz użyć TransactionTemplate
zamiast transakcji deklaratywnych:
public class A {
@Autowired
TransactionTemplate transactionTemplate;
public void method() {
transactionTemplate.execute(status -> {
A();
B();
return null;
});
}
...
}
Aktualizacja
Odpowiadając na zaktualizowane pytanie OP, korzystając z powyższych informacji.
Którą metodę należy opisać za pomocą @Transactional:changes()? DatabaseChanges()?
@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Upewnij się, że changes()
jest wywoływana „z zewnątrz” ziarna, a nie z samej klasy i po utworzeniu instancji kontekstu (np. nie jest to afterPropertiesSet()
lub @PostConstruct
metoda z adnotacjami). Zrozum, że domyślnie wycofuje ona transakcję tylko dla niesprawdzonych wyjątków (spróbuj być bardziej szczegółowy w liście cofnięć dla zaznaczonych wyjątków).