Spring - @Transactional - เกิดอะไรขึ้นในพื้นหลัง


334

ฉันต้องการที่จะรู้ว่าสิ่งที่เกิดขึ้นจริงเมื่อคุณอธิบายวิธีการด้วย@Transactionalหรือไม่ แน่นอนฉันรู้ว่า Spring จะปิดวิธีการนั้นในธุรกรรม

แต่ฉันมีข้อสงสัยดังต่อไปนี้:

  1. ฉันได้ยินมาว่า Spring สร้างคลาสพร็อกซีหรือไม่ คนที่สามารถอธิบายเรื่องนี้ในเชิงลึก มีอยู่จริงในพร็อกซีคลาสนั้น เกิดอะไรขึ้นกับชั้นเรียนจริง และฉันจะดูชั้นพร็อกซีที่สร้างขึ้นของสปริงได้อย่างไร
  2. ฉันยังอ่านในเอกสาร Spring ที่:

หมายเหตุ: เนื่องจากกลไกนี้จะขึ้นอยู่กับผู้รับมอบฉันทะเท่านั้น 'ภายนอก' วิธีการโทรเข้ามาผ่านพร็อกซี่จะถูกดัก ซึ่งหมายความว่า 'การภาวนาด้วยตนเอง' เช่นวิธีการภายในวัตถุเป้าหมายที่เรียกใช้วิธีการอื่นของวัตถุเป้าหมายจะไม่นำไปสู่การทำธุรกรรมจริงที่รันไทม์แม้ว่าจะมีการทำเครื่องหมายด้วยวิธีการที่เรียก@Transactional!

ที่มา: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

เหตุใดการเรียกใช้เมธอดภายนอกเท่านั้นจึงจะอยู่ภายใต้ธุรกรรมและไม่ใช่วิธีเรียกใช้ด้วยตนเอง


2
การสนทนาที่เกี่ยวข้องอยู่ที่นี่: stackoverflow.com/questions/3120143/…
dma_k

คำตอบ:


255

นี่เป็นหัวข้อใหญ่ Spring Reference doc ให้การอ้างอิงหลายบท ฉันขอแนะนำให้อ่านสิ่งที่เกี่ยวกับการเขียนโปรแกรมและธุรกรรมที่เน้นการใช้งานเนื่องจากการสนับสนุนธุรกรรมที่เป็นประกาศของสปริงใช้ AOP ที่รากฐาน

แต่ในระดับที่สูงมาก Spring สร้างพร็อกซีสำหรับคลาสที่ประกาศ@Tractactionalในชั้นเรียนหรือสมาชิก พร็อกซีส่วนใหญ่จะไม่ปรากฏที่รันไทม์ มันมีวิธีสำหรับ Spring ในการฉีดพฤติกรรมก่อนหลังหรือรอบ ๆ การเรียกเมธอดไปยังวัตถุที่ถูกพร็อกซี การจัดการธุรกรรมเป็นเพียงตัวอย่างหนึ่งของพฤติกรรมที่สามารถเชื่อมต่อได้การตรวจสอบความปลอดภัยเป็นอีกเรื่องหนึ่ง และคุณสามารถจัดเตรียมสิ่งต่าง ๆ เช่นการบันทึกด้วยตนเองได้เช่นกัน ดังนั้นเมื่อคุณใส่คำอธิบายประกอบด้วย@Transactional Spring จะสร้างพร็อกซีที่ใช้อินเทอร์เฟซเดียวกับคลาสที่คุณกำลังเพิ่มความคิดเห็น และเมื่อลูกค้าทำการโทรเข้าไปในวัตถุของคุณการโทรจะถูกดักและพฤติกรรมที่ถูกส่งผ่านกลไกพร็อกซี

ธุรกรรมใน EJB ทำงานในทำนองเดียวกัน

ตามที่คุณสังเกตผ่านกลไกพร็อกซีจะทำงานเฉพาะเมื่อมีสายเข้ามาจากวัตถุภายนอกบางอย่าง เมื่อคุณทำการโทรภายในภายในวัตถุคุณจะทำการโทรผ่านการอ้างอิงแบบ " นี่ " ซึ่งข้ามพร็อกซี อย่างไรก็ตามมีวิธีแก้ไขปัญหานี้อยู่ ฉันอธิบายวิธีหนึ่งในโพสต์ฟอรัมนี้ซึ่งฉันใช้BeanFactoryPostProcessorเพื่อฉีดอินสแตนซ์ของพร็อกซีลงในคลาส "อ้างอิงตนเอง" ที่รันไทม์ ฉันบันทึกการอ้างอิงนี้ไปยังตัวแปรสมาชิกที่เรียกว่า " ฉัน " ถ้าฉันต้องการโทรภายในที่ต้องการการเปลี่ยนแปลงสถานะของเธรดฉันจะทำการโทรผ่านพร็อกซี (เช่น " me.someMethod ()"".) โพสต์ฟอรัมอธิบายในรายละเอียดเพิ่มเติมโปรดทราบว่ารหัสBeanFactoryPostProcessorจะแตกต่างกันเล็กน้อยในขณะนี้เนื่องจากเขียนกลับมาในกรอบ Spring 1.x แต่หวังว่ามันจะช่วยให้คุณมีความคิด ฉันอาจทำให้พร้อมใช้งาน


4
>> พร็อกซี่ส่วนใหญ่จะมองไม่เห็นที่รันไทม์โอ้ !! ฉันอยากรู้อยากเห็นพวกเขา :) ส่วนที่เหลือ .. คำตอบของคุณครอบคลุมมาก นี่เป็นครั้งที่สองที่คุณช่วยฉัน .. ขอบคุณสำหรับความช่วยเหลือทั้งหมด
จุดสูงสุด

17
ไม่มีปัญหา. คุณสามารถดูรหัสพร็อกซีได้หากคุณผ่านขั้นตอนการดีบักเกอร์ นั่นอาจเป็นวิธีที่ง่ายที่สุด ไม่มีเวทย์มนตร์ พวกเขาเป็นเพียงคลาสภายในแพ็คเกจ Spring
Rob H

และหากวิธีการที่มีคำอธิบายประกอบ @Transaction กำลังใช้งานอินเตอร์เฟสสปริงจะใช้ไดนามิกพร็อกซี API เพื่อฉีดการทำธุรกรรมและไม่ใช้พรอกซี ฉันต้องการให้คลาส transactionalised ของฉันใช้อินเทอร์เฟซในกรณีใด ๆ
Michael Wiles

1
ฉันพบรูปแบบ "ฉัน" ด้วย (ใช้การเดินสายที่ชัดเจนเพื่อทำตามที่ฉันคิด) แต่ฉันคิดว่าถ้าคุณทำแบบนั้นคุณอาจดีกว่าการปรับโครงสร้างใหม่เพื่อที่คุณจะไม่ ต้อง. แต่ใช่ว่าบางครั้งอาจจะอึดอัดใจมาก!
Donal Fellows

2
2019: ในฐานะที่เป็นคำตอบนี้จะได้รับเก่าโพสต์ฟอรั่มเรียกว่าไม่สามารถใช้ได้อีกซึ่งจะอธิบายถึงกรณีที่เมื่อคุณต้องโทรภายในวัตถุโดยไม่ต้องBeanFactoryPostProcessor ผ่านพร็อกซี่ที่ใช้ อย่างไรก็ตามมีวิธีที่คล้ายกันมาก (ตามความเห็นของฉัน) ที่อธิบายไว้ในคำตอบนี้: stackoverflow.com/a/11277899/3667003 ... และวิธีแก้ปัญหาเพิ่มเติมในเธรดทั้งหมดเช่นกัน
Z3d4s

196

เมื่อฤดูใบไม้ผลิโหลดคำจำกัดความถั่วของคุณและได้รับการกำหนดค่าให้มองหา@Transactionalคำอธิบายประกอบก็จะสร้างเหล่านี้วัตถุพร็อกซีรอบที่แท้จริงของคุณถั่ว วัตถุพร็อกซีเหล่านี้เป็นอินสแตนซ์ของคลาสที่สร้างขึ้นอัตโนมัติเมื่อรันไทม์ พฤติกรรมเริ่มต้นของวัตถุพร็อกซี่เหล่านี้เมื่อมีการเรียกใช้วิธีการก็คือการเรียกใช้วิธีการเดียวกันกับ "เป้าหมาย" ถั่ว (เช่นถั่วของคุณ)

อย่างไรก็ตามพร็อกซีสามารถจัดหาให้กับ interceptors และเมื่อนำเสนอ interceptors เหล่านี้จะถูกเรียกโดยพร็อกซี่ก่อนที่จะเรียกวิธีการเป้าหมายถั่วของคุณ สำหรับถั่วเป้าหมายที่มีคำอธิบายประกอบด้วย@TransactionalSpring จะสร้างTransactionInterceptorและส่งผ่านไปยังวัตถุพร็อกซีที่สร้างขึ้น ดังนั้นเมื่อคุณเรียกใช้เมธอดจากรหัสไคลเอนต์คุณกำลังเรียกใช้เมธอดบนพร็อกซีออบเจ็กต์ซึ่งจะเรียกใช้งานTransactionInterceptor(ซึ่งเริ่มทรานแซคชัน) ก่อน เมื่อการร้องขอเสร็จสิ้นการยอมรับTransactionInterceptor/ ย้อนกลับการทำธุรกรรม มันโปร่งใสกับรหัสลูกค้า

สำหรับสิ่งที่ "วิธีการภายนอก" ถ้าถั่วของคุณเรียกใช้หนึ่งในวิธีการของตัวเองแล้วมันจะไม่ทำเช่นนั้นผ่านทางพร็อกซี โปรดจำไว้ว่า Spring ห่อถั่วในพร็อกซีถั่วของคุณไม่มีความรู้ โทรจาก "นอก" ถั่วของคุณเท่านั้นที่ผ่านพร็อกซี

มันช่วยได้ไหม


36
> โปรดจำไว้ว่าสปริงห่อถั่วของคุณในพร็อกซีถั่วของคุณไม่มีความรู้เรื่องนี้สิ่งที่กล่าวมาทั้งหมด ช่างเป็นคำตอบที่ยอดเยี่ยม ขอบคุณสำหรับการช่วยเหลือ.
จุดสูงสุด

คำอธิบายที่ยอดเยี่ยมสำหรับพร็อกซีและตัวดัก ตอนนี้ฉันเข้าใจว่า spring ใช้วัตถุพร็อกซีเพื่อสกัดการเรียกไปยัง bean เป้าหมาย ขอบคุณ!
dharag

ฉันคิดว่าคุณกำลังพยายามอธิบายรูปภาพของเอกสาร Spring และการเห็นภาพนี้ช่วยฉันได้มาก: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/ ......
WesternGun

44

ในฐานะบุคคลที่มองเห็นฉันชอบชั่งน้ำหนักด้วยไดอะแกรมลำดับของรูปแบบพร็อกซี หากคุณไม่ทราบวิธีการอ่านลูกศรที่ผมอ่านคนแรกเช่นนี้รันClientProxy.method()

  1. ลูกค้าเรียกวิธีการตามเป้าหมายจากมุมมองของเขาและถูกสกัดกั้นโดยพร็อกซีอย่างเงียบ ๆ
  2. หากมีการกำหนดแง่มุมก่อนหน้าพร็อกซีจะดำเนินการ
  3. จากนั้นจะดำเนินการตามวิธีการจริง (เป้าหมาย)
  4. After-Return และ After-Throwing เป็นลักษณะทางเลือกที่ดำเนินการหลังจากที่เมธอดส่งคืนและ / หรือถ้าเมธอดเกิดข้อยกเว้น
  5. หลังจากนั้นพร็อกซีจะเรียกใช้งานรูปแบบ after (หากกำหนดไว้)
  6. ในที่สุดพร็อกซีกลับไปที่ลูกค้าโทร

แผนภาพลำดับของรูปแบบพร็อกซี (ฉันได้รับอนุญาตให้โพสต์ภาพตามเงื่อนไขที่ฉันพูดถึงต้นกำเนิดของมันผู้แต่ง: Noel Vaes, เว็บไซต์: www.noelvaes.eu)


27

คำตอบที่ง่ายที่สุดคือ:

วิธีการใดก็ตามที่คุณประกาศ@Transactionalขอบเขตของการทำธุรกรรมเริ่มต้นและขอบเขตสิ้นสุดลงเมื่อวิธีการเสร็จสมบูรณ์

หากคุณใช้การโทร JPA การกระทำทั้งหมดจะอยู่ในขอบเขตการทำธุรกรรมนี้

ให้บอกว่าคุณกำลังบันทึกเอนทิตี้ 1, เอนทิตี 2 และเอนทิตี 3 ตอนนี้ในขณะที่บันทึกเอนทิตี 3 เกิดข้อยกเว้นดังนั้นเมื่อ enitiy1 และเอนทิตี 2 มาในการทำธุรกรรมเดียวกันดังนั้นเอนทิตี 1 และเอนทิตี 2 จะถูกย้อนกลับด้วยเอนทิตี 3

ธุรกรรม:

  1. entity1.save
  2. entity2.save
  3. entity3.save

ข้อยกเว้นใด ๆ จะส่งผลให้เกิดการย้อนกลับของธุรกรรม JPA ทั้งหมดที่มีฐานข้อมูลธุรกรรม JPA ภายในถูกใช้โดย Spring


2
"ข้อยกเว้น A willn̶y̶จะส่งผลให้เกิดการย้อนกลับของธุรกรรม JPA ทั้งหมดที่มี DB" หมายเหตุเฉพาะ RuntimeException ส่งผลให้ย้อนกลับ ตรวจสอบข้อยกเว้นจะไม่ส่งผลให้ย้อนกลับ
Arjun

2

อาจจะช้า แต่ฉันเจอสิ่งที่อธิบายข้อกังวลของคุณที่เกี่ยวข้องกับพร็อกซี (เฉพาะการเรียกใช้เมธอด 'ภายนอก' ที่เข้ามาทางพร็อกซีจะถูกดัก) อย่างดี

ตัวอย่างเช่นคุณมีคลาสที่มีลักษณะเช่นนี้

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

และคุณมีแง่มุมที่มีลักษณะเช่นนี้:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

เมื่อคุณดำเนินการเช่นนี้:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

ผลลัพธ์ของการโทร kickOff ด้านบนให้รหัสด้านบน

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

แต่เมื่อคุณเปลี่ยนรหัสเป็น

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

คุณจะเห็นวิธีการภายในเรียกวิธีอื่นภายในเพื่อที่จะไม่ถูกดักและเอาท์พุทจะมีลักษณะเช่นนี้:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

คุณสามารถทำสิ่งนี้ได้โดยทำสิ่งนั้น

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

ตัวอย่างโค้ดที่นำมาจาก: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/


1

คำตอบที่มีอยู่ทั้งหมดถูกต้อง แต่ฉันรู้สึกไม่สามารถให้หัวข้อที่ซับซ้อนนี้ได้

สำหรับคำอธิบายที่ครอบคลุมและเป็นประโยชน์คุณอาจต้องการอ่านคู่มือเชิงลึกของSpring @Transactionalซึ่งพยายามอย่างดีที่สุดเพื่อครอบคลุมการจัดการธุรกรรมด้วยคำง่าย ๆ ~ 4000 คำพร้อมตัวอย่างโค้ดจำนวนมาก


มันเยี่ยมมาก ...
Alpit Anand
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.