वसंत - @ प्रत्यावर्तन - पृष्ठभूमि में क्या होता है?


334

मैं जानना चाहता हूं कि वास्तव में क्या होता है जब आप किसी विधि के साथ टिप्पणी करते हैं @Transactional? बेशक, मुझे पता है कि वसंत एक लेनदेन में उस विधि को लपेटेंगे।

लेकिन, मुझे निम्नलिखित संदेह हैं:

  1. मैंने सुना है कि स्प्रिंग एक प्रॉक्सी क्लास बनाता है ? क्या कोई इसे अधिक गहराई से समझा सकता है । वास्तव में उस प्रॉक्सी वर्ग में क्या रहता है? क्या होता है वास्तविक वर्ग? और मैं स्प्रिंग की निर्मित अनुमानित कक्षा कैसे देख सकता हूं
  2. मैंने स्प्रिंग डॉक्स में भी पढ़ा है कि:

नोट: चूंकि यह तंत्र परदे के पीछे आधारित है, इसलिए प्रॉक्सी के माध्यम से आने वाली केवल 'बाहरी' विधि कॉल को इंटरसेप्ट किया जाएगा । इसका मतलब है कि 'सेल्फ-इनवोकेशन', यानी टारगेट ऑब्जेक्ट के भीतर एक ऐसी विधि जिसे टारगेट ऑब्जेक्ट का कोई और तरीका कहा जाता है, तब भी रनटाइम पर वास्तविक ट्रांजैक्शन नहीं होगा, भले ही इनवॉइस मेथड चिह्नित हो @Transactional!

स्रोत: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

केवल बाहरी विधि कॉल लेन-देन के तहत ही होंगी और स्व-आह्वान विधियों के लिए नहीं?


2
प्रासंगिक चर्चा यहां है: stackoverflow.com/questions/3120143/…
dma_k

जवाबों:


255

यह एक बड़ा विषय है। स्प्रिंग संदर्भ डॉक्टर ने इसके लिए कई अध्याय समर्पित किए हैं। मैं ऐस्पेक्ट-ओरिएंटेड प्रोग्रामिंग और लेनदेन पर लोगों को पढ़ने की सलाह देता हूं , क्योंकि स्प्रिंग की डिक्लेक्टिव ट्रांजैक्शन सपोर्ट AOP का इस्तेमाल करती है।

लेकिन एक बहुत ही उच्च स्तर पर, स्प्रिंग उन वर्गों के लिए भविष्यद्वाणी बनाता है, जो वर्ग पर या सदस्यों पर @ प्रत्यावर्तन की घोषणा करते हैं। प्रॉक्सी ज्यादातर रनटाइम पर अदृश्य होता है। यह स्प्रिंग को व्यवहार में आने से पहले, उसके बाद, या आस-पास के तरीकों को इंजेक्ट करने का एक तरीका प्रदान करता है। लेन-देन प्रबंधन उन व्यवहारों का केवल एक उदाहरण है जिसमें हुक किया जा सकता है। सुरक्षा जांच एक और है। और आप लॉगिंग जैसी चीजों के लिए अपना खुद का भी प्रदान कर सकते हैं। इसलिए जब आप @Transactional के साथ किसी विधि को एनोटेट करते हैं , तो स्प्रिंग गतिशील रूप से एक प्रॉक्सी बनाता है जो आपके द्वारा एनोटेट करने वाले वर्ग के समान इंटरफ़ेस (ओं) को लागू करता है। और जब ग्राहक आपकी वस्तु में कॉल करते हैं, तो कॉल इंटरसेप्ट होती है और प्रॉक्सी तंत्र के माध्यम से इंजेक्शन व्यवहार किया जाता है।

EJB में लेनदेन इसी तरह से काम करते हैं।

जैसा कि आपने देखा, किसी बाहरी वस्तु से कॉल आने पर प्रॉक्सी तंत्र केवल तभी काम करता है। जब आप ऑब्जेक्ट के भीतर एक आंतरिक कॉल करते हैं, तो आप वास्तव में " यह " संदर्भ के माध्यम से कॉल कर रहे हैं , जो प्रॉक्सी को बायपास करता है। हालांकि, उस समस्या के आसपास काम करने के तरीके हैं। मैं इस मंच पोस्ट में एक दृष्टिकोण की व्याख्या करता हूं जिसमें मैं रनटाइम पर "स्वयं-संदर्भित" कक्षाओं में प्रॉक्सी के एक उदाहरण को इंजेक्ट करने के लिए एक बीनफैक्टरीपोस्टप्रोसेसर का उपयोग करता हूं । मैं इस संदर्भ को " मुझे " नामक एक सदस्य चर में सहेजता हूं । फिर अगर मुझे आंतरिक कॉल करने की आवश्यकता है जो थ्रेड के लेनदेन की स्थिति में बदलाव की आवश्यकता है, तो मैं कॉल को प्रॉक्सी के माध्यम से निर्देशित करता हूं (जैसे " me.someMethod ())"।) फ़ोरम पोस्ट अधिक विस्तार से बताती है। ध्यान दें कि बीनफैक्टरीपोस्टप्रोसेसर कोड अब थोड़ा अलग होगा, क्योंकि यह स्प्रिंग 1.x टाइमफ्रेम में वापस लिखा गया था। लेकिन उम्मीद है कि यह आपको एक विचार देता है। मेरे पास एक अद्यतन जानकारी है। मैं शायद उपलब्ध कर सकता था।


4
>> प्रॉक्सी ज्यादातर रनटाइम पर अदृश्य होता है ओह !! मैं उन्हें देखने के लिए उत्सुक हूं :) बाकी .. आपका जवाब बहुत व्यापक था। यह दूसरी बार है जब आप मेरी मदद कर रहे हैं..सभी मदद के लिए धन्यवाद।
चोटी

17
कोई दिक्कत नहीं है। यदि आप डिबगर के साथ कदम रखते हैं तो आप प्रॉक्सी कोड देख सकते हैं। शायद यही सबसे आसान तरीका है। कोई जादू नहीं है; वे बस स्प्रिंग पैकेज के भीतर कक्षाएं हैं।
रॉब एच

और अगर @Transaction एनोटेशन वाली विधि एक इंटरफ़ेस को लागू कर रही है तो वसंत गतिशील प्रॉक्सी एपीआई का उपयोग लेनदेन को इंजेक्ट करने के लिए करेगा और कि प्रॉक्सी का उपयोग करने के लिए । मैं अपने लेन-देन वाले वर्गों को किसी भी मामले में इंटरफेस लागू करना पसंद करता हूं।
माइकल विल्स

1
मुझे "मुझे" योजना भी मिली (स्पष्ट वायरिंग का उपयोग करते हुए इसे वैसे ही करना चाहिए जैसा मैं सोचता हूं), लेकिन मुझे लगता है कि यदि आप इस तरह से कर रहे हैं, तो आप शायद बेहतर तरीके से रिफैक्टिंग कर रहे हैं ताकि आप न करें यह करना है। लेकिन हाँ, यह कभी-कभी बहुत अजीब हो सकता है!
डोनाल्ड फेलो

2
2019: जैसे-जैसे यह उत्तर पुराना होता जा रहा है, संदर्भित फ़ोरम पोस्ट उपलब्ध नहीं है जो इस मामले का वर्णन करेगा जब आपको प्रॉक्सी का उपयोग किए बिना ऑब्जेक्ट के भीतर एक आंतरिक कॉल करना होगा , उपयोग करनाBeanFactoryPostProcessor । हालाँकि, इस उत्तर में वर्णित (मेरे विचार में) एक समान विधि है: stackoverflow.com/a/11277899/3667003 ... और साथ ही पूरे सूत्र में आगे के समाधान भी।
Z3d4s

195

जब स्प्रिंग आपकी बीन परिभाषाओं को लोड करता है, और @Transactionalएनोटेशन देखने के लिए कॉन्फ़िगर किया गया है , तो यह आपके वास्तविक बीन के चारों ओर इन प्रॉक्सी ऑब्जेक्ट को बनाएगा । ये प्रॉक्सी ऑब्जेक्ट उन कक्षाओं के उदाहरण हैं जो रनटाइम पर ऑटो-जनरेट किए जाते हैं। एक विधि लागू होने पर इन प्रॉक्सी वस्तुओं का डिफ़ॉल्ट व्यवहार "लक्ष्य" बीन (यानी आपके बीन) पर एक ही विधि को लागू करने के लिए है।

हालाँकि, प्रॉक्सी को इंटरसेप्टर के साथ भी आपूर्ति की जा सकती है, और जब ये इंटरसेप्टर आपके लक्ष्य बीन के तरीके को लागू करने से पहले प्रॉक्सी द्वारा इनवॉइस किया जाएगा। लक्ष्य बीन्स के साथ एनोटेट किया जाता है @Transactional, स्प्रिंग ए बनायेगा TransactionInterceptor, और इसे उत्पन्न प्रॉक्सी ऑब्जेक्ट को पास करेगा। इसलिए जब आप क्लाइंट कोड से विधि को कॉल करते हैं, तो आप प्रॉक्सी ऑब्जेक्ट पर विधि को कॉल कर रहे हैं, जो पहले TransactionInterceptor(जो एक लेनदेन शुरू करता है), जो बदले में आपके लक्ष्य बीन पर विधि को आमंत्रित करता है। जब मंगलाचरण समाप्त हो जाता है, TransactionInterceptorतो लेनदेन वापस करता है। यह क्लाइंट कोड के लिए पारदर्शी है।

"बाहरी विधि" चीज़ के लिए, यदि आपका बीन अपने स्वयं के तरीकों में से एक को आमंत्रित करता है, तो यह प्रॉक्सी के माध्यम से ऐसा नहीं करेगा। याद रखें, वसंत आपके सेम को प्रॉक्सी में लपेटता है, आपके सेम को इसका कोई ज्ञान नहीं है। केवल "बाहर" से कॉल करने से आपका बीन प्रॉक्सी से गुजरता है।

क्या उससे मदद हुई?


36
> याद रखें, स्प्रिंग आपके बीन को प्रॉक्सी में लपेटता है, आपके बीन को इसका कोई ज्ञान नहीं है । क्या शानदार जवाब है। मदद के लिए धन्यवाद।
चोटी 39

प्रॉक्सी और इंटरसेप्टर्स के लिए शानदार व्याख्या। अब मैं समझता हूं कि वसंत एक लक्ष्य बीन के लिए कॉल को अवरोधन के लिए एक प्रॉक्सी वस्तु को लागू करता है। धन्यवाद!

मुझे लगता है कि आप वसंत प्रलेखन के इस चित्र का वर्णन करने के प्रयास कर रहे हैं और इस तस्वीर को देखकर मुझे बहुत मदद करता है: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/...
WesternGun

44

एक दृश्य व्यक्ति के रूप में, मुझे प्रॉक्सी पैटर्न के अनुक्रम आरेख के साथ तौलना पसंद है। यदि आप तीर को पढ़ना नहीं जानते हैं, तो मैं इस तरह से पहला पाठ पढ़ता हूं: Clientनिष्पादित करता है Proxy.method()

  1. क्लाइंट अपने दृष्टिकोण से लक्ष्य पर एक विधि कहता है, और चुपचाप प्रॉक्सी द्वारा अवरोधन करता है
  2. यदि एक पूर्व पहलू परिभाषित किया गया है, तो प्रॉक्सी इसे निष्पादित करेगा
  3. फिर, वास्तविक विधि (लक्ष्य) को निष्पादित किया जाता है
  4. वापसी के बाद और फेंकने के बाद वैकल्पिक पहलू होते हैं जो विधि रिटर्न के बाद निष्पादित होते हैं और / या यदि विधि एक अपवाद फेंकता है
  5. उसके बाद, प्रॉक्सी पहलू के बाद निष्पादित होता है (यदि परिभाषित हो)
  6. अंत में प्रॉक्सी कॉलिंग क्लाइंट में वापस आ जाता है

प्रॉक्सी पैटर्न अनुक्रम आरेख (मुझे इस शर्त पर फोटो पोस्ट करने की अनुमति थी कि मैंने इसकी उत्पत्ति का उल्लेख किया है। लेखक: नोएल वेस, वेबसाइट: www.noelvaes.eu)


27

सबसे सरल उत्तर है:

आप जो भी विधि घोषित @Transactionalकरते हैं, लेनदेन की सीमा शुरू होती है और विधि पूरी होने पर सीमा समाप्त हो जाती है।

यदि आप जेपीए कॉल का उपयोग कर रहे हैं तो सभी लेनदेन इस लेनदेन की सीमा में हैं

कहते हैं कि आप निकाय 1, इकाई 2 और निकाय 3 को बचा रहे हैं। अब जब Unit3 को सहेजते समय एक अपवाद उत्पन्न होता है , तो जैसा कि enitiy1 और Unit2 समान लेनदेन में आता है, इसलिए unit1 और unit2 होगा रोलबैक किया जाएगा।

लेन-देन:

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

किसी भी अपवाद के परिणामस्वरूप DB के साथ सभी JPA लेनदेन का रोलबैक होगा। बाह्य रूप से JPA लेनदेन का उपयोग स्प्रिंग द्वारा किया जाता है।


2
"Willn ofy̶ अपवाद से DB के साथ सभी जेपीए लेनदेन का रोलबैक होगा।" नोट रोलबैक में केवल RuntimeException परिणाम। अपवाद की जाँच की, रोलबैक में परिणाम नहीं होगा।
अर्जुन

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);
   }

}

ऊपर दिए गए कोड से किकऑफ कॉल करने के परिणाम।

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-proc/


0

सभी मौजूदा उत्तर सही हैं, लेकिन मुझे लगता है कि यह जटिल विषय नहीं दे सकता।

एक व्यापक, व्यावहारिक व्याख्या के लिए, आप इस स्प्रिंग @Transactional इन-डेप्थ गाइड पर एक नज़र डालना चाहते हैं, जो बहुत सारे कोड उदाहरणों के साथ ~ 4000 सरल शब्दों में लेनदेन प्रबंधन को कवर करने की पूरी कोशिश करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.