Wróćmy do problemu z poprzedniego wpisu:

Na początku przypomnijmy szybko co robiła adnotacja @Transactional – mocno uproszczając: otwierała nam transakcję przed wywołaniem metody oznaczonej tą adnotacją i zamykała transakcje po zakończeniu działania metody. Może prościej zobrazuje to poniższy fragment kodu.
Zapis do bazy bez wykorzystania adnotacji:
public void save(User user) throws SQLException{
entityManager.getTransaction().begin();
try {
return entityManager.persist(user);
} finally {
entityManager.getTransaction().commit();
entityManager.close();
}
}
Oraz po wykorzystaniu adnotacji:
@Transactional
public void save(User user) {
return entityManager.save(user);
}
Widzimy przy okazji jak bardzo oszczędza nam to pisanie powtarzalnego kodu. 🙂
Spis treści
- Pierwsza myśl – wyciągnijmy powtarzalny kod „wyżej”
- ThreadLocal – czyli po co jest nam to w ogóle potrzebne?
- Dodajemy adnotację @Transactional
- Odpowiedź na pytanie – czemu adnotacja @Transactional w tym przypadku nie ma sensu?
Wyciągnijmy powtarzalny kod
Dosyć normalna reakcja – widzimy powtarzalny kod i zaświeca nam się lampka, że coś trzeba z nim zrobić.
public void save(User user) throws SQLException{
entityManager.getTransaction().begin();
try {
return entityManager.persist(user);
} finally {
entityManager.getTransaction().commit();
entityManager.close();
}
}
Tak na prawdę wszystko oprócz entityManager.persist(user); powtarza się za każdym razem. Wyciągnijmy to więc do osobnej metody wykorzystując coś na kształt metody szablonowej.
public interface TransactionTemplate {
<T> T execute(TransactionalOperation<T> action) throws SQLException;
}
public class TransactionTemplateImpl implements TransactionTemplate {
@Override
public <T> T execute(Function<T> action) throws SQLException {
// pobierz wcześniej w jakiś sposób entityManagera
// [...]
entityManager.getTransaction().begin();
try {
// dokładnie nasze entityManager.persist(..);
return action.run();
} finally {
entityManager.getTransaction().commit();
entityManager.close();
}
}
}
I widzimy, że zrealizowaliśmy jakiś tam mały cel – redukcji powtarzającego się kodu. No prawie… Zostaje nam jeszcze kwestia tego skąd wziąć tego EntityManagera.
ThreadLocal – czyli po co jest nam to w ogóle potrzebne?
Pierwsza myśl jaka nam przychodzi – zawołajmy o niego bezpośrednio z EntityManagerFactory. O ile utworzenie fabryki jest bardzo zasobożerne tak zawołanie po samego EntityManagera jest już stosunkowo tanie. 🙂
Tyle, że zawsze jest jakieś ale. Taka praktyka (EntityManager per operacja) jest po prostu zła z kilku powodów:
https://docs.jboss.org/hibernate/core/4.0/hem/en-US/html/transactions.html
Najczęściej wykorzystywaną praktyką w aplikacjach client/server jest EntityManager per request. Oznacza to, że za każdym zapytaniem do serwera otwierany jest nowy EntityManager. I tutaj z pomocą przychodzi nam konstrukcja ThreadLocal, która pozwala nam na przechowywanie danych per wątek. W większości serwerów każdy request jest osobnym wątkiem. Więcej o tym można poczytać How do servlets handle multiple requests?
Each request is processed in a separated thread. This doesn’t mean tomcat creates a thread per request. There a is pool of threads to process requests.
https://www.quora.com/How-do-servlets-handle-multiple-requests
Skoro już wiemy skąd będziemy pobierać EntityManager to możemy przejść do kodu.
Prosta klasa do przechowywania naszego EntityManagera:
public class EMThreadLocalStorage {
private static final ThreadLocal<EntityManager> threadLocal = new ThreadLocal<>();
public static EntityManager getEntityManager() {
return threadLocal.get();
}
public static void setEntityManager(EntityManager entityManager) {
threadLocal.set(entityManager);
}
}
I dodatkowo zaktualizujmy wcześniejszą metodę o brakujący fragment:
public class TransactionTemplate implements TransactionTemplate {
@Override
public <T> T execute(Function<T> action) throws SQLException {
EntityManager entityManager = EMThreadLocalStorage.getEntityManager();
entityManager.getTransaction().begin();
try {
// dokładnie nasze entityManager.persist(..);
return action.run();
} finally {
entityManager.getTransaction().commit();
entityManager.close();
}
}
}
Dodajemy adnotację @Transactional
Przejdźmy do kolejnego kroku. Chcemy, aby nasza metoda była wykonywana w TransactionTemplate w momencie gdy jest oznaczona adnotacją @Transactional. Zaczniemy od utworzenia samej adnotacji:
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {}
Teraz będę opierał się na kodzie z poprzedniego wpisu. Będziemy do trochę rozbudowywać. Zacznijmy od metody getBean. W tym przypadku dojdzie następująca zmiana. Musimy opakować nasz obiekt w proxy.
public <T> T getBean(Class<T> clazz) {
try {
T obj = getBeanFromConfiguration(clazz);
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Inject.class)) {
field.setAccessible(true);
field.set(obj, getBean(field.getType()));
}
}
if (clazz.isInterface()) {
// opakowujemy nasz obiekt w proxy transakcyjności - szablonem jest nasz TransactionTemplateImpl()
TransactionalHandler transactionalHandler = new TransactionalHandler(obj, new TransactionTemplateImpl());
obj = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, transactionalHandler);
}
return obj;
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
Opakowujemy nasz obiekt w proxy TransactionHandler, którego zadaniem jest sprawdzenie, przy każdym wywołaniu metody danego obiektu, czy czasem nie jest ona (ta metoda lub klasa) oznaczona adnotacją @Transactional. Jeżeli tak jest to chcemy, aby została ona wywołana w obrębie transakcji – czyli opakowana w naszego TransactionTemplate. W tym celu nasza klasa musi implementować interfejs InvocationHandler. O dynamicznym proxy więcej poczytasz Java dynamic proxies.
public class TransactionalHandler implements InvocationHandler {
private Object object;
private TransactionTemplate transactionTemplate;
public TransactionalHandler(Object object, TransactionTemplate transactionTemplate) {
super();
this.transactionTemplate = transactionTemplate;
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable, InvocationTargetException, IllegalAccessException, IllegalArgumentException {
Class<?> clazz = object.getClass();
boolean runInTransaction = clazz.isAnnotationPresent(Transactional.class) || clazz.getMethod(method.getName(), method.getParameterTypes()).isAnnotationPresent(Transactional.class);
// jezeli klasa lub metoda oznaczona @Transactional to uruchom w TransactionTemplate
if (runInTransaction) {
return transactionTemplate.execute(() -> {
try {
return method.invoke(object, args);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
});
} else {
return method.invoke(object, args);
}
}
}
Czemu adnotacja @Transactional w tym przypadku nie ma sensu?
Wróćmy do pytania z samego początku wypisu.

Czy widzisz już czemu to nie zadziała? 🙂
Metoda secondMethod wywoływana jest bezpośrednio z tej samej klasy (metody save). Oznacza to, że pomiędzy tymi dwoma metodami nie ma żadnego proxy*. Brakuje tutaj tej warstwy, która stwierdzi: O! Tutaj jest adnotacja @Transactional, a to oznacza że muszę odpalić tę metodę w transakcji.
Przedstawiając to obrazowo:


*Mowa tutaj o najpopularniejszej sytuacji – proxy Springa (a on wykorzystuje mechanizm JDK dynamic proxies or CGLIB – do wyboru) – tutaj więcej do poczytania o mechanizmie proxy w Springu
Całość kodu jak zwykle na GitHubie.