Wstęp
Zarządzanie transakcjami jest możliwe za pomocą adnotacji @Transactonal, @UserTransactional, oraz @SharkTransactional. Adnotacje te możemy używać tylko w klasach zarządzanych przez kontekst aplikacji.
- @Transactional - tworzy transakcję Hibernate
- @SharkTransactional - tworzy transakcję SharkTransaction
- @UserTransactional - tworzy transakcję UserTransaction
Dobre praktyki
Zaleca się stosowanie adnotacji do zarządzania transakcjami w klasach oznaczonych adnotacją @Service
W klasach, które nie są zarządzane przez kontekst aplikacji możemy wywoływać nasze bloki kodu również wewnątrz jednej transakcji wykorzystując do tego odpowiednie metody wrappujące z klasy TransactionWrapper (od wersji 3.2.76). We wcześniejszych wersjach należy skorzystać bezpośrednio z TransactionTemplate.
Należy pamiętać, że wszystkie operacje związane z bazą danych, które wykonujemy w danym zadaniu, powinny odbywać się wewnątrz jednej transakcji. Dzięki temu nasz kod będzie transakcyjny oraz będzie się szybciej wykonywać.
Przykłady użycia z wykorzystaniem adnotacji
@Service public class CustomServiceImpl implements CustomService { @Autowired private StructureService structureService; @Transactional public void addPositionAndOu() { Position p = new Position( "name", "symbol" ); OrganizationalUnit ou = new OrganizationalUnit(); ou.setName( "name" ); ou.setSymbol( "symbol" ); structureService.createPosition( p ); structureService.createOrganizationalUnit( ou ); } public void addPositionAndOuNonTransactional() { Position p = new Position( "name", "symbol" ); OrganizationalUnit ou = new OrganizationalUnit(); ou.setName( "name" ); ou.setSymbol( "symbol" ); structureService.createPosition( p ); structureService.createOrganizationalUnit( ou ); } }
W powyższym przykładzie funkcja bez adnotacji @Transactional utworzy osobne transakcje dla każdej funkcji save, natomiast w funkcji oznaczonej @Transactional obie operacje będą działać na jednej tej samej transakcji.
Manualne zarządzanie transakcjami
Jeżeli potrzebujemy wykonać blok kodu wewnątrz aktywnej transakcji hibernate lub sharkowej oraz mieć dostęp do sesji i transakcji, to należy skorzystać z klasy TransactionWrapper.
TransactionWrapper.get().doInHibernateTransaction( ( session ) -> { QueryExecutor qe = ComponentFactory.getQueryExecutor(); SQLQuery sql = qe.createSQLQuery( ... ); ... sql.executeUpdate(); });
TransactionWrapper.get().doInSharkTransaction( ( sharkTransaction ) -> { ActivityService activityService = ServiceFactory.getActivityService(); Map<String, Object> activityContext = activityService.getActivityContext( processId, activityId ); activityContext.put( "id_zmiennej", "nowa_wartość" ); activityService.setActivityContext( processId, activityId, activityContext ); });
Nasz kod może również zwracać dowolny wynik.
Manualne zarządzanie transakcjami poniżej wersji 3.2.76 (od wersji 3.2.76 należy korzystać z wcześniejszego punktu)
Istnieje możliwość manualnego zarządzania transakcjami w przypadkach gdy potrzebujemy większej kontroli lub potrzebujemy ich w obszarze kodu, w którym nie mamy możliwości skorzystania z adnotacji opisanych powyżej.
W tym celu możemy wykorzystać klasę TransactionManagerFactory
TransactionTemplate tt = new TransactionTemplate( TransactionManagerFactory.getHibernateTransactionManager() ); tt.execute( new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult( TransactionStatus status ) { QueryExecutor qe = ComponentFactory.getQueryExecutor(); SQLQuery sql = qe.createSQLQuery( ... ); ... sql.executeUpdate(); } } );
Jeżeli potrzebujemy mieć dostęp do obiektu Session od Hibernate, należy TransactionTemplate wykorzysać następująco:
TransactionTemplate tt = new TransactionTemplate( TransactionManagerFactory.getHibernateTransactionManager() ); tt.execute( new SessionAwareTransactionCallbackWithoutResult() { @Override public void doWithSession( Session session ) { session.delete( ... ); } } );
final SharkTransactionManager mgr= TransactionManagerFactory.getSharkTransactionManager(); TransactionTemplate tt = new TransactionTemplate( mgr ); tt.execute( new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult( TransactionStatus status ) { SharkTransaction sharkTransaction = mgr.getSharkTransaction(); ... } } );
SharkTransactionManager mgr = TransactionManagerFactory.getSharkTransactionManager(); TransactionStatus txStatus = null; try { txStatus = mgr.getTransaction( new DefaultTransactionDefinition() ); SharkTransaction sharkTransaction = mgr.getSharkTransaction(); ...//wykonywanie operacji na transakcji mgr.commit( txStatus ); } catch ( Exception ex ) { mgr.rollback( txStatus ); }
SharkTransactionTemplate
Klasa com.suncode.pwfl.transaction.support.SharkTransactionTemplate
ułatwia zarządzanie transakcją Shark'ową w czytelny i bezpieczny sposób.
Wszędzie tam, gdzie potrzebujemy transakcję Shark'ową powinniśmy wykorzystywać klasę SharkTransactionTemplate ze względu na łatwiejsze jej użycie. Obsługa kodu transakcyjnego może być skomplikowana i wymaga uważnego traktowania.
Jeżeli kod wykorzystujący transakcję zwraca wynik, wywołanie będzie wyglądać następująco:
SharkTransactionTemplate tx = new SharkTransactionTemplate(); int result = tx.execute( new SharkTransactionCallback<Integer>() { @Override public Integer doInSharkTransaction( SharkTransaction transaction, TransactionStatus status ) throws Exception { // wykorzystanie transakcji return 1; } } );
W przypadku, gdy wywołujemy procedurę która nie zwraca żadnego wyniku, możemy skorzystać z innego callback'u SharkTransactionCallbackWithoutResult
:
SharkTransactionTemplate tx = new SharkTransactionTemplate(); tx.execute( new SharkTransactionCallbackWithoutResult() { @Override public void doInSharkTransactionWithoutResult( SharkTransaction transaction, TransactionStatus status ) throws Exception { // wykorzystanie transakcji } } );
Domyślna konfiguracja SharkTransactionTemplate
wykorzystuje aktualną transakcję, jeżeli jest dostępna lub otwiera nową.
Przydatne zasoby: