Odpowiedzi:
Dobre pytanie, choć nie jest trywialne.
Określa relacje między transakcjami. Typowe opcje:
Required
: Kod zawsze będzie działał w transakcji. Tworzy nową transakcję lub wykorzystuje ją ponownie, jeśli jest dostępna.Requires_new
: Kod zawsze będzie działał w nowej transakcji. Zawiesza bieżącą transakcję, jeśli taka istnieje.Definiuje umowę danych między transakcjami.
Read Uncommitted
: Pozwala na brudne odczyty.Read Committed
: Nie zezwala na brudne odczyty.Repeatable Read
: Jeśli wiersz zostanie odczytany dwukrotnie w tej samej transakcji, wynik zawsze będzie taki sam.Serializable
: Wykonuje wszystkie transakcje po kolei.Różne poziomy mają różne parametry wydajnościowe w aplikacji wielowątkowej. Myślę, że jeśli zrozumiesz dirty reads
pojęcie, będziesz mógł wybrać dobrą opcję.
Przykład wystąpienia brudnego odczytu:
thread 1 thread 2
| |
write(x) |
| |
| read(x)
| |
rollback |
v v
value (x) is now dirty (incorrect)
Tak więc rozsądnym ustawieniem domyślnym (jeśli można tak twierdzić) może być Read Committed
tylko odczytanie wartości, które zostały już zatwierdzone przez inne uruchomione transakcje, w połączeniu z poziomem propagacji wynoszącym Required
. Następnie możesz pracować stamtąd, jeśli Twoja aplikacja ma inne potrzeby.
Praktyczny przykład tego, gdzie nowa transakcja będzie zawsze tworzona przy wchodzeniu do provideService
rutyny i zakończona przy wychodzeniu:
public class FooService {
private Repository repo1;
private Repository repo2;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void provideService() {
repo1.retrieveFoo();
repo2.retrieveFoo();
}
}
Gdybyśmy skorzystali zamiast tego Required
, transakcja pozostałaby otwarta, gdyby transakcja była już otwarta podczas wprowadzania procedury. Należy również zauważyć, że wynik a rollback
może być inny, ponieważ kilka egzekucji może brać udział w tej samej transakcji.
Możemy łatwo zweryfikować zachowanie za pomocą testu i zobaczyć, jak wyniki różnią się w zależności od poziomów propagacji:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {
private @Autowired TransactionManager transactionManager;
private @Autowired FooService fooService;
@Test
public void testProvideService() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
fooService.provideService();
transactionManager.rollback(status);
// assert repository values are unchanged ...
}
Z poziomem propagacji wynoszącym
Requires new
: spodziewalibyśmy się, że NIEfooService.provideService()
został wycofany, ponieważ stworzył własną sub-transakcję.
Required
: spodziewalibyśmy się, że wszystko zostało wycofane, a sklep z podkładami pozostał niezmieniony.
sessionFactory.getCurrentTransaction()
został dodany, nie trzeba HibernateTemplate
już uruchamiać zarządzania transakcjami. Usunąłem go :)
PROPAGATION_REQUIRED = 0 ; Jeśli DataSourceTransactionObject T1 jest już uruchomiony dla metody M1. Jeśli dla innej metody M2 wymagany jest obiekt transakcji, nie jest tworzony nowy obiekt transakcji. Dla obiektu M2 używany jest ten sam obiekt T1
PROPAGATION_MANDATORY = 2 ; Metoda musi działać w ramach transakcji. Jeśli żadna istniejąca transakcja nie jest w toku, zostanie zgłoszony wyjątek
PROPAGATION_REQUIRES_NEW = 3 ; Jeśli DataSourceTransactionObject T1 jest już uruchomiony dla metody M1 i jest w toku (wykonywanie metody M1). Jeśli inna metoda M2 zacznie działać, wówczas T1 zostanie zawieszony na czas metody M2 z nowym DataSourceTransactionObject T2 dla M2.M2 uruchomionym w ramach własnego kontekstu transakcji
PROPAGATION_NOT_SUPPORTED = 4 ; Jeśli DataSourceTransactionObject T1 jest już uruchomiony dla metody M1. Jeśli inna metoda M2 jest uruchomiona jednocześnie. Następnie M2 nie powinien działać w kontekście transakcji. T1 jest zawieszony do czasu ukończenia M2.
PROPAGATION_NEVER = 5 ; Żadna z metod nie działa w kontekście transakcji.
Poziom izolacji: Chodzi o to, jak duży wpływ na transakcję mogą mieć działania innych równoczesnych transakcji. Zapewnia spójność, pozostawiając dane w wielu tabelach w spójnym stanie. Polega na blokowaniu wierszy i / lub tabel w bazie danych.
Problem z wieloma transakcjami
Scenariusz 1. Jeżeli transakcja T1 odczytuje dane z tabeli A1, która została zapisana przez inną równoległą transakcję T2, jeżeli w drodze wycofywania T2 dane otrzymane przez T1 są nieprawidłowe, np. Dane a = 2 są danymi oryginalnymi. Jeśli T1 odczytał a = 1, który został napisany przez T2. Jeśli wycofanie T2, a = 1 zostanie cofnięte do a = 2 w DB, ale teraz T1 ma a = 1, ale w tabeli DB jest zmienione na a = 2.
Scenariusz 2. Jeśli transakcja T1 odczytuje dane z tabeli A1. Jeśli inna transakcja równoległa (T2) aktualizuje dane w tabeli A1. Następnie dane odczytane przez T1 różnią się od tabeli A1. Ponieważ T2 zaktualizowało dane w tabeli A1. przeczytaj a = 1 i T2 zaktualizowane a = 2. Następnie a! = b.
Scenariusz 3. Jeżeli transakcja T1 odczytuje dane z tabeli A1 z określoną liczbą wierszy. Jeśli inna jednoczesna transakcja (T2) wstawi więcej wierszy w tabeli A1. Liczba wierszy odczytanych przez T1 różni się od wierszy w tabeli A1
Scenariusz 1 nosi nazwę Brudne odczyty.
Scenariusz 2 nazywa się odczytami niepowtarzalnymi.
Scenariusz 3 nosi nazwę Odczyty fantomowe.
Zatem poziom izolacji to zakres, w którym można zapobiec Scenariuszowi 1, Scenariuszowi 2, Scenariuszowi 3 . Całkowity poziom izolacji można uzyskać, stosując blokowanie. Zapobiega to jednoczesnemu odczytywaniu i zapisywaniu tych samych danych, ale wpływa na wydajność. Poziom izolacji zależy od aplikacji wymaganej izolacji.
ISOLATION_READ_UNCOMMITTED : Pozwala odczytać zmiany, które nie zostały jeszcze zatwierdzone. Dotyczy scenariusza 1, scenariusza 2, scenariusza 3
ISOLATION_READ_COMMITTED : Pozwala na odczyty z jednoczesnych transakcji, które zostały zatwierdzone. Może to dotyczyć scenariusza 2 i scenariusza 3. Ponieważ inne transakcje mogą aktualizować dane.
ISOLATION_REPEATABLE_READ : Wiele odczytów tego samego pola da te same wyniki, dopóki nie zostanie zmienione samodzielnie. Może to mieć wpływ na Scenariusz 3. Ponieważ inne transakcje mogą wstawiać dane
ISOLATION_SERIALIZABLE : Scenariusz 1, Scenariusz 2, Scenariusz 3 nigdy się nie zdarza. Jest to całkowita izolacja. Obejmuje pełne blokowanie. Ma zdolność wykonywania z powodu blokowania.
Możesz przetestować za pomocą
public class TransactionBehaviour {
// set is either using xml Or annotation
DataSourceTransactionManager manager=new DataSourceTransactionManager();
SimpleTransactionStatus status=new SimpleTransactionStatus();
;
public void beginTransaction()
{
DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
// overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
// set is either using xml Or annotation
manager.setPropagationBehavior(XX);
manager.setIsolationLevelName(XX);
status = manager.getTransaction(Def);
}
public void commitTransaction()
{
if(status.isCompleted()){
manager.commit(status);
}
}
public void rollbackTransaction()
{
if(!status.isCompleted()){
manager.rollback(status);
}
}
Main method{
beginTransaction()
M1();
If error(){
rollbackTransaction()
}
commitTransaction();
}
}
Możesz debugować i zobaczyć wynik z różnymi wartościami izolacji i propagacji.
Wystarczające wyjaśnienie każdego parametru podano w innych odpowiedziach; Jakkolwiek prosiłeś o przykład z prawdziwego świata, oto ten, który wyjaśnia cel różnych opcji propagacji :
Załóżmy, że jesteś odpowiedzialny za wdrożenie usługi rejestracji, w której użytkownik otrzymuje wiadomość e-mail z potwierdzeniem. Pojawiają się dwa obiekty usługowe, jeden do rejestracji użytkownika i jeden do wysyłania wiadomości e-mail, który ten drugi nazywany jest wewnątrz pierwszego. Na przykład coś takiego:/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService{
...
void SignUp(User user){
...
emailService.sendMail(User);
}
}
/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService{
...
void sendMail(User user){
try{
... // Trying to send the e-mail
}catch( Exception)
}
}
Być może zauważyłeś, że druga usługa ma typ propagacji REQUIRES_NEW, a ponadto istnieje prawdopodobieństwo, że zgłasza wyjątek (wyłączenie serwera SMTP, nieprawidłowa wiadomość e-mail lub inne przyczyny). Prawdopodobnie nie chcesz, aby cały proces był wycofywany, np. usuwanie informacji o użytkowniku z bazy danych lub innych rzeczy; dlatego wywołujesz drugą usługę w oddzielnej transakcji.
Wracając do naszego przykładu, tym razem martwisz się bezpieczeństwem bazy danych, więc zdefiniuj swoje klasy DAO w ten sposób:/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
// some CRUD methods
}
Oznacza to, że za każdym razem, gdy tworzony jest obiekt DAO, a tym samym potencjalny dostęp do db, musimy zapewnić, że połączenie zostało wykonane z jednej z naszych usług, co oznacza, że transakcja na żywo powinna istnieć; w przeciwnym razie wystąpi wyjątek, dlatego propagacja jest typu OBOWIĄZKOWA .
Poziom izolacji określa, w jaki sposób zmiany wprowadzone w niektórych repozytoriach danych przez jedną transakcję wpływają na inne jednoczesne transakcje, a także w jaki sposób i kiedy te zmienione dane stają się dostępne dla innych transakcji. Kiedy definiujemy transakcję za pomocą frameworka Spring, jesteśmy również w stanie skonfigurować, na jakim poziomie izolacji ta sama transakcja zostanie wykonana.
@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {
}
Poziom izolacji READ_UNCOMMITTED stwierdza, że transakcja może odczytać dane, które są nadal niezatwierdzone przez inne transakcje.
Poziom izolacji READ_COMMITTED stwierdza, że transakcja nie może odczytać danych, które nie zostały jeszcze zatwierdzone przez inne transakcje.
Poziom izolacji REPEATABLE_READ stwierdza, że jeśli transakcja odczytuje jeden rekord z bazy danych wiele razy, wynik wszystkich tych operacji odczytu musi być zawsze taki sam.
SERIALIZABLE poziom izolacji jest najbardziej restrykcyjnym ze wszystkich poziomów izolacji. Transakcje są wykonywane z blokowaniem na wszystkich poziomach (blokowanie odczytu, zakresu i zapisu), dzięki czemu wydają się być wykonywane w sposób szeregowy.
Propagacja to zdolność do decydowania o tym, w jaki sposób metody biznesowe powinny być zawarte w logicznych lub fizycznych transakcjach.
Zachowanie WYMAGANE na wiosnę oznacza, że ta sama transakcja zostanie użyta, jeśli istnieje już otwarta transakcja w bieżącym kontekście wykonania metody komponentu bean.
Zachowanie REQUIRES_NEW oznacza, że kontener zawsze tworzy nową fizyczną transakcję.
Zachowanie NESTED powoduje, że zagnieżdżone transakcje sprężynowe używają tej samej transakcji fizycznej, ale ustawia punkty zapisu między zagnieżdżonymi wywołaniami, aby transakcje wewnętrzne mogły również być wycofywane niezależnie od transakcji zewnętrznych.
Zachowanie OBOWIĄZKOWE stwierdza, że istniejąca otwarta transakcja musi już istnieć. Jeśli nie, wyjątek zostanie zgłoszony przez kontener.
Zachowanie NIGDY nie stanowi, że istniejąca otwarta transakcja nie może już istnieć. Jeśli istnieje transakcja, kontener zgłosi wyjątek.
Zachowanie NOT_SUPPORTED zostanie wykonane poza zakresem jakiejkolwiek transakcji. Jeśli otwarta transakcja już istnieje, zostanie ona wstrzymana.
Zachowanie WSPIERA zostanie wykonane w zakresie transakcji, jeśli otwarta transakcja już istnieje. Jeśli nie ma już otwartej transakcji, metoda zostanie wykonana mimo wszystko w sposób nietransakcyjny.
Transakcja reprezentuje jednostkę pracy z bazą danych.
W TransactionDefinition
interfejsie wiosennym , który definiuje właściwości transakcji zgodne ze sprężyną. @Transactional
adnotacja opisuje atrybuty transakcji w metodzie lub klasie.
@Autowired
private TestDAO testDAO;
@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {
// Interact with testDAO
}
Rozmnażanie (reprodukcja): służy do relacji między transakcjami. (analogicznie do komunikacji między wątkami Java)
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value | Propagation | Description |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| -1 | TIMEOUT_DEFAULT | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
| 0 | PROPAGATION_REQUIRED | Support a current transaction; create a new one if none exists. |
| 1 | PROPAGATION_SUPPORTS | Support a current transaction; execute non-transactionally if none exists. |
| 2 | PROPAGATION_MANDATORY | Support a current transaction; throw an exception if no current transaction exists. |
| 3 | PROPAGATION_REQUIRES_NEW | Create a new transaction, suspending the current transaction if one exists. |
| 4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally. |
| 5 | PROPAGATION_NEVER | Do not support a current transaction; throw an exception if a current transaction exists. |
| 6 | PROPAGATION_NESTED | Execute within a nested transaction if a current transaction exists. |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
Izolacja: Izolacja jest jedną z właściwości ACID (Atomowość, Spójność, Izolacja, Trwałość) transakcji w bazie danych. Izolacja określa, w jaki sposób integralność transakcji jest widoczna dla innych użytkowników i systemów. Używa do blokowania zasobów, tj. Kontroli współbieżności, upewnij się, że tylko jedna transakcja może uzyskać dostęp do zasobu w danym punkcie.
Postrzeganie blokady: poziom izolacji określa czas trwania blokady.
+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode | Read | Insert | Update | Lock Scope |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED | uncommitted data | Allowed | Allowed | No Lock |
| READ_COMMITTED (Default) | committed data | Allowed | Allowed | Lock on Committed data |
| REPEATABLE_READ | committed data | Allowed | Not Allowed | Lock on block of table |
| SERIALIZABLE | committed data | Not Allowed | Not Allowed | Lock on full table |
+---------------------------+-------------------+-------------+-------------+------------------------+
Czytaj postrzeganie: występują następujące 3 rodzaje głównych problemów:
UPDATES
z innego tx.INSERTS
i / lub DELETES
z innego txPoziomy izolacji z różnymi rodzajami odczytów:
+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode | Dirty reads | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED | allows | allows | allows |
| READ_COMMITTED (Default) | prevents | allows | allows |
| REPEATABLE_READ | prevents | prevents | allows |
| SERIALIZABLE | prevents | prevents | prevents |
+---------------------------+----------------+----------------------+----------------+
Prawie nigdy nie chcesz używać, Read Uncommited
ponieważ nie jest tak naprawdę ACID
zgodny. Read Commmited
jest dobrym domyślnym miejscem początkowym. Repeatable Read
jest prawdopodobnie potrzebny tylko w scenariuszach raportowania, zestawienia lub agregacji. Zauważ, że wiele baz danych, w tym postgres, w rzeczywistości nie obsługuje powtarzalnego odczytu, musisz Serializable
zamiast tego użyć . Serializable
jest przydatny w przypadku rzeczy, o których wiesz, że muszą się zdarzyć całkowicie niezależnie od wszystkiego innego; pomyśl o tym jak synchronized
w Javie. Serializowalność idzie w parze z REQUIRES_NEW
propagacją.
Używam REQUIRES
do wszystkich funkcji, które uruchamiają zapytania UPDATE lub DELETE, a także funkcji poziomu usług. W przypadku funkcji poziomu DAO, które uruchamiają tylko SELECT, używam, SUPPORTS
która będzie uczestniczyć w TX, jeśli jest już uruchomiona (tj. Wywołana z funkcji serwisowej).
Izolacja transakcji i propagacja transakcji, chociaż są powiązane, ale wyraźnie są to dwie bardzo różne koncepcje. W obu przypadkach wartości domyślne są dostosowywane w komponencie granicy klienta za pomocą Deklaratywnego zarządzania transakcjami lub Programowego zarządzania transakcjami . Szczegóły dotyczące każdego poziomu izolacji i atrybutów propagacji można znaleźć w odnośnikach poniżej.
W przypadku danych dwóch lub więcej działających transakcji / połączeń z bazą danych, jak i kiedy zmiany dokonywane przez zapytania w jednej transakcji wpływają / są widoczne dla zapytań w innej transakcji. Odnosiło się to również do tego, jakiego rodzaju blokowanie rekordów bazy danych zostanie wykorzystane do odizolowania zmian w tej transakcji od innych transakcji i odwrotnie. Zazwyczaj jest to realizowane przez bazę danych / zasób uczestniczący w transakcji.
.
W aplikacji korporacyjnej dla dowolnego żądania / przetwarzania istnieje wiele komponentów, które są zaangażowane w wykonanie zadania. Niektóre z tych komponentów oznaczają granice (początek / koniec) transakcji, która będzie używana w odpowiednim komponencie i jego podskładnikach. W przypadku tej granicy transakcyjnej komponentów Propogacja transakcji określa, czy dany komponent będzie uczestniczył w transakcji, a co się stanie, jeśli wywołujący komponent już ma lub nie ma już utworzonej / uruchomionej transakcji. Jest to to samo, co atrybuty transakcji Java EE. Zazwyczaj jest to realizowane przez menedżera transakcji / połączeń klienta.
Odniesienie:
Mam biegać outerMethod
, method_1
a method_2
w trybie innym propagacji.
Poniżej przedstawiono dane wyjściowe dla innego trybu propagacji.
Metoda zewnętrzna
@Transactional
@Override
public void outerMethod() {
customerProfileDAO.method_1();
iWorkflowDetailDao.method_2();
}
Metoda_1
@Transactional(propagation=Propagation.MANDATORY)
public void method_1() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "XXX");
session.save(entity);
System.out.println("Method - 1 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
Metoda 2
@Transactional()
@Override
public void method_2() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "CCC");
session.save(entity);
int i = 1/0;
System.out.println("Method - 2 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
Możemy dodać do tego:
@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {
public Customer getDetail(String customername) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateCustomer(Customer customer) {
// do something
}
}
Możesz użyć w ten sposób:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}
Możesz użyć tej rzeczy również:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}