התפשטות עסקאות ובידוד באביב @ Transactional

1. הקדמה

במדריך זה נסקור את @ Transactional ביאור שלה בידוד ו רְבִיָה הגדרות.

2. מה זה @ Transactional?

אנחנו יכולים להשתמש @ Transactional לעטוף שיטה בעסקת מסד נתונים.

זה מאפשר לנו לקבוע תנאי התפשטות, בידוד, פסק זמן, קריאה בלבד והחזר כספי לעסקה שלנו. כמו כן, אנו יכולים לציין את מנהל העסקאות.

2.1. @ Transactional פרטי היישום

Spring יוצר פרוקסי או מתפעל את קוד בת הכיתה כדי לנהל את היצירה, ההתחייבות וההחזר של העסקה. במקרה של מיופה כוח, ספרינג מתעלם @ Transactional בשיחות שיטה פנימיות.

במילים פשוטות, אם יש לנו שיטה כמו callMethod ואנחנו מסמנים את זה כ- @ Transactional, אביב יעטוף קוד ניהול של עסקאות סביב ההפעלה:@ Transactional שיטה הנקראת:

createTransactionIfNecessary (); נסה {callMethod (); commitTransactionAfterReturning (); } לתפוס (חריג) {completeTransactionAfterThrowing (); לזרוק חריג; }

2.2. איך להישתמש @ Transactional

אנו יכולים לשים את ההערה על הגדרות ממשקים, מחלקות או ישירות על שיטות. הם עוקפים זה את זה לפי סדר העדיפויות; מהנמוך לגבוה ביותר שיש לנו: ממשק, סופר קלאס, מחלקה, שיטת ממשק, שיטת סופר-קלאס ושיטת מחלקה.

אביב מיישם את ההערה ברמה הכיתתית על כל השיטות הציבוריות של הכיתה הזו שלא הערנו איתן @ Transactional .

עם זאת, אם נשים את ההערה על שיטה פרטית או מוגנת, אביב יתעלם ממנה ללא שגיאה.

נתחיל בדוגמת ממשק:

ממשק ציבורי @ Transactional TransferService {העברת חלל (משתמש מחרוזת 1, משתמש מחרוזת 2, שווי כפול); } 

בדרך כלל, לא מומלץ להגדיר את @ Transactional על הממשק. עם זאת, זה מקובל במקרים כמו מאגר @ עם נתוני אביב.

אנו יכולים לשים את ההערה על הגדרת מחלקה כדי לעקוף את הגדרת העסקה של הממשק / סופר-קלאס:

@Service @Transactional public class TransferServiceImpl מיישם את TransferService {@ העברת חלל ריקים ציבורי (מחרוזת משתמש 1, מחרוזת משתמש 2, שווי כפול) {// ...}}

עכשיו בואו נעקוף אותו על ידי הגדרת ההערה ישירות על השיטה:

@ העברת חלל ציבורית ציבורי (מחרוזת משתמש 1, מחרוזת משתמש 2, ערך כפול) {// ...}

3. התפשטות עסקאות

ריבוי מגדיר את הגבול העסקה של ההיגיון העסקי שלנו. אביב מצליח להתחיל ולהשהות עסקה לפי שלנו רְבִיָה הגדרה.

אביב קורא TransactionManager :: getTransaction להשיג או ליצור עסקה בהתאם להתרבות. הוא תומך בחלק מהתפשטות לכל סוגי TransactionManager, אך ישנם כמה מהם שתומכים רק ביישומים ספציפיים של TransactionManager.

עכשיו בואו נעבור את ההפצות השונות וכיצד הם עובדים.

3.1. נדרש רְבִיָה

נדרש הוא התפשטות ברירת המחדל. אביב בודק אם קיימת עסקה פעילה ואז הוא יוצר עסק חדש אם לא היה קיים דבר. אחרת, ההיגיון העסקי מצורף לעסקה הפעילה כרגע:

@Transactional (propagation = Propagation.REQUIRED) חלל ציבורי חובה לדוגמא (משתמש מחרוזת) {// ...}

גם כ נדרש הוא התפשטות ברירת המחדל, אנו יכולים לפשט את הקוד על ידי הפלתו:

@ חובה ציבורית ריקנית עסקית נדרשת לדוגמא (משתמש מחרוזת) {// ...}

בואו נראה את קוד הפסאוד של אופן פעולת יצירת העסקאות נדרש רְבִיָה:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } להחזיר את הקיים; } להחזיר createNewTransaction ();

3.2. תומכים רְבִיָה

ל תומכים, האביב בודק תחילה אם קיימת עסקה פעילה. אם קיימת עסקה, ישמש את העסקה הקיימת. אם אין עסקה, היא מבוצעת ללא עסקה:

@Transactional (propagation = Propagation.SUPPORTS) חלל ציבורי תומך לדוגמא (משתמש מחרוזת) {// ...}

בואו נראה את קוד הפסאודו של יצירת העסקה עבור תומכים:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } להחזיר את הקיים; } להחזיר ריק תנועה;

3.3. חובה רְבִיָה

כאשר ההפצה היא חובה, אם יש עסקה פעילה, היא תשמש אותה. אם אין עסקה פעילה, ספרינג מציג חריג:

@Transactional (propagation = Propagation.MANDATORY) ריק בטל חובה לדוגמא (משתמש מחרוזת) {// ...}

ובואו ונראה שוב את קוד הפסאודו:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } להחזיר את הקיים; } לזרוק IllegalTransactionStateException;

3.4. לעולם לא רְבִיָה

להיגיון עסקי עם לעולם לא התפשטות, אביב מעניק חריג אם יש עסקה פעילה:

@Transactional (propagation = Propagation.NEVER) חלל ציבורי neverExample (משתמש מחרוזת) {// ...}

בואו נראה את קוד הפסאודו של האופן שבו פועלת יצירת עסקאות לעולם לא רְבִיָה:

אם (isExistingTransaction ()) {זרוק IllegalTransactionStateException; } החזר ריקTransaction;

3.5. אינו נתמך רְבִיָה

האביב משעה תחילה את העסקה הנוכחית אם היא קיימת, ואז ההיגיון העסקי מבוצע ללא עסקה.

@Transactional (propagation = Propagation.NOT_SUPPORTED) חלל ציבורי notSupportedExample (משתמש מחרוזת) {// ...}

ה JTATransactionManager תומך בהשעיית עסקאות אמיתית מחוץ לקופסה. אחרים מדמים את ההשעיה על ידי החזקת הפניה לקיים ואז ניקוי מהקשר החוט

3.6. REQUIRES_NEW רְבִיָה

כאשר ההפצה היא REQUIRES_NEW, אביב משעה את העסקה הנוכחית אם היא קיימת ואז יוצר עסק חדש:

@Transactional (propagation = Propagation.REQUIRES_NEW) חלל ציבורי מחייב NewExample (משתמש מחרוזת) {// ...}

דומה ל אינו נתמך, אנחנו צריכים את JTATransactionManager להשעיית עסקה בפועל.

והקוד הפסאודו נראה כך:

אם (isExistingTransaction ()) {להשעות (קיים); נסה {return createNewTransaction (); } לתפוס (חריג) {resumeAfterBeginException (); לזרוק חריג; }} להחזיר createNewTransaction ();

3.7. מקונן רְבִיָה

ל מקונן התפשטות, אביב בודק אם קיימת עסקה, אז אם כן, זה מסמן נקודת שמירה. פירוש הדבר שאם ביצוע ההיגיון העסקי שלנו יוצא חריג, אזי החזרת עסקאות לנקודת החיסכון הזו. אם אין עסקה פעילה, זה עובד כמו נדרש .

DataSourceTransactionManager תומך בהפצה זו מחוץ לקופסה. כמו כן, כמה יישומים של JTATransactionManager עשוי לתמוך בכך.

JpaTransactionManager תומך מקונן רק לחיבורי JDBC. עם זאת, אם נקבע nestedTransactionAllowed דגל ל נָכוֹן, זה עובד גם עבור קוד גישה JDBC בעסקאות JPA אם מנהל ההתקן JDBC שלנו תומך בנקודות שמירה.

לבסוף, בואו נקבע את רְבִיָה ל מקונן:

@Transactional (propagation = Propagation.NESTED) public void nestedExample (משתמש מחרוזת) {// ...}

4. בידוד עסקה

בידוד הוא אחד המאפיינים הנפוצים של חומצה: אטומיות, עקביות, בידוד ועמידות. בידוד מתאר כיצד שינויים המופעלים על ידי עסקאות מקבילות גלויים זה לזה.

כל רמת בידוד מונעת אפס או יותר תופעות לוואי במקביל לעסקה:

  • קריאה מלוכלכת: קרא את השינוי הבלתי מחויב של עסקה במקביל
  • קריאה בלתי חוזרת: קבל ערך שונה בקריאה חוזרת של שורה אם עסקה בו זמנית מעדכנת את אותה שורה ומתחייבת
  • קרא פנטום: קבל שורות שונות לאחר ביצוע מחדש של שאילתת טווח אם עסקה אחרת מוסיפה או מסירה שורות מסוימות בטווח ומתחייבת

אנו יכולים לקבוע את רמת הבידוד של עסקה לפי @ Transactional :: בידוד. יש בו חמש ספירות באביב: בְּרִירַת מֶחדָל, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, ניתן לסדר.

4.1. ניהול בידוד באביב

רמת הבידוד המוגדרת כברירת מחדל היא בְּרִירַת מֶחדָל. לכן כאשר אביב יוצר עסקה חדשה, רמת הבידוד תהיה בידוד ברירת המחדל של ה- RDBMS שלנו. לכן עלינו להיזהר אם נשנה את מסד הנתונים.

עלינו לשקול גם מקרים בהם אנו מכנים שרשרת של שיטות עם בידוד שונה. בתזרים הרגיל, הבידוד חל רק כאשר נוצרה עסקה חדשה. לפיכך אם מסיבה כלשהי איננו רוצים לאפשר שיטה לבצע בבידוד שונה, עלינו להגדיר TransactionManager :: setValidateExistingTransaction לאמיתי. ואז קוד הפסאודו לאימות העסקה יהיה:

אם (isolationLevel! = ISOLATION_DEFAULT) {if (currentTransactionIsolationLevel ()! = isolationLevel) {throw IllegalTransactionStateException}}

עכשיו בואו להעמיק ברמות בידוד שונות והשפעותיהן.

4.2. READ_UNCOMMITTED בידוד

READ_UNCOMMITTED היא רמת הבידוד הנמוכה ביותר ומאפשרת גישה בו זמנית ביותר.

כתוצאה מכך הוא סובל משלוש תופעות הלוואי המקבילות. אז עסקה עם בידוד זה קוראת נתונים לא מחויבים של עסקאות מקבילות אחרות. כמו כן, הן קריאות שאינן ניתנות לחזרה והן קריאות פנטום יכולות לקרות. כך אנו יכולים לקבל תוצאה אחרת בקריאה חוזרת של שורה או ביצוע מחדש של שאילתת טווח.

אנחנו יכולים להגדיר את בידוד רמה לשיטה או כיתה:

@Transactional (בידוד = בידוד. READ_UNCOMMITTED) יומן חלל ריק (הודעת מחרוזת) {// ...}

Postgres אינו תומך READ_UNCOMMITTED בידוד ונופל חזרה ל במקום זאת, READ_COMMITED. כמו כן, אורקל אינה תומכת ומאפשרת READ_UNCOMMITTED.

4.3. READ_COMMITTED בידוד

הרמה השנייה של בידוד, READ_COMMITTED, מונע קריאות מלוכלכות.

שאר תופעות הלוואי במקביל עדיין יכולות לקרות. לכן לשינויים לא מחויבים בעסקאות מקבילות אין כל השפעה עלינו, אך אם עסקה תבצע את שינויים, התוצאה שלנו עשויה להשתנות על ידי שאילתות חוזרות.

הנה, הגדרנו את בידוד רָמָה:

@Transactional (isolation = Isolation.READ_COMMITTED) יומן חלל ריק (הודעת מחרוזת) {// ...}

READ_COMMITTED היא רמת ברירת המחדל עם Postgres, SQL Server ו- Oracle.

4.4. REPEATABLE_READ בידוד

הרמה השלישית של בידוד, REPEATABLE_READ, מונע קריאות מלוכלכות ולא חוזרות. כך שאיננו מושפעים משינויים בלתי מחויבים בעסקאות במקביל.

כמו כן, כאשר אנו מבררים שוב שורה, אנו לא מקבלים תוצאה אחרת. אך בביצוע מחדש של שאילתות טווח, אנו עשויים לקבל שורות שנוספו או הוסרו לאחרונה.

יתר על כן, זו הרמה הנמוכה ביותר הנדרשת כדי למנוע את העדכון האבוד. העדכון האבוד מתרחש כאשר שתי עסקאות מקבילות או יותר קוראות ומעדכנות את אותה שורה. REPEATABLE_READ אינו מאפשר גישה בו זמנית לשורה כלל. מכאן שהעדכון האבוד לא יכול לקרות.

כך מגדירים את בידוד רמה לשיטה:

@Transactional (isolation = Isolation.REPEATABLE_READ) יומן הריקים הציבורי (הודעת מחרוזת) {// ...}

REPEATABLE_READ היא רמת ברירת המחדל ב- Mysql. אורקל אינה תומכת REPEATABLE_READ.

4.5. ניתן לסדר בידוד

ניתן לסדר היא הרמה הגבוהה ביותר של בידוד. זה מונע את כל תופעות הלוואי המקבילות שהוזכרו, אך יכול להוביל לשיעור הגישה המקביל הנמוך ביותר מכיוון שהוא מבצע שיחות במקביל ברצף.

במילים אחרות, ביצוע מקביל של קבוצה של עסקאות מסודרות יש את אותה תוצאה כמו ביצוען באופן סדרתי.

עכשיו בואו נראה איך להגדיר ניתן לסדר כמו בידוד רָמָה:

@Transactional (בידוד = Isolation.SERIALIZABLE) יומן ריק לציבור (הודעת מחרוזת) {// ...}

5. מסקנה

במדריך זה חקרנו את תכונת ההפצה של @עִסקָה בפירוט. לאחר מכן למדנו על תופעות לוואי במקביל ורמות בידוד.

כמו תמיד, תוכל למצוא את הקוד השלם ב- GitHub.