उसी वर्ग के भीतर विधि द्वारा स्प्रिंग @Transaction मेथड कॉल, काम नहीं करता है?


109

मैं वसंत लेनदेन के लिए नया हूं। कुछ ऐसा जो मुझे वास्तव में अजीब लगा, शायद मुझे यह ठीक से समझ में आया।

मैं विधि स्तर के आसपास एक लेन-देन करना चाहता था और मेरे पास एक ही कक्षा में एक कॉलर पद्धति है और ऐसा लगता है कि यह पसंद नहीं है, इसे अलग वर्ग से बुलाया जाना है। मुझे समझ नहीं आता कि यह कैसे संभव है।

अगर किसी को इस मुद्दे को हल करने का कोई विचार है, तो मैं बहुत सराहना करूंगा। मैं एनोटेट ट्रांजेक्शनल विधि को कॉल करने के लिए उसी वर्ग का उपयोग करना चाहूंगा।

यहाँ कोड है:

public class UserService {

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
            // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                    .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            addUser(user.getUserName, user.getPassword);
        }
    } 
}

TransactionTemplateदृष्टिकोण पर एक नज़र डालें : stackoverflow.com/a/52989925/355438
Lu55

सेल्फ इनवोकेशन क्यों काम नहीं करता है, इसके बारे में 8.6 प्रॉक्सिमिंग मैकेनिज्म देखें ।
जेसन लॉ

जवाबों:


99

यह स्प्रिंग AOP (गतिशील वस्तुओं और cglib ) की एक सीमा है ।

यदि आप लेनदेन को संभालने के लिए AspectJ का उपयोग करने के लिए स्प्रिंग को कॉन्फ़िगर करते हैं, तो आपका कोड काम करेगा।

सरल और शायद सबसे अच्छा विकल्प अपने कोड को फिर से भरना है। उदाहरण के लिए एक वर्ग जो उपयोगकर्ताओं को संभालता है और एक वह जो प्रत्येक उपयोगकर्ता को संसाधित करता है। फिर स्प्रिंग AOP के साथ डिफॉल्ट ट्रांजेक्शन हैंडलिंग काम करेगा।


AspectJ के साथ लेनदेन को संभालने के लिए कॉन्फ़िगरेशन युक्तियाँ

लेनदेन के लिए AspectJ का उपयोग करने के लिए स्प्रिंग को सक्षम करने के लिए, आपको AspectJ को मोड सेट करना होगा:

<tx:annotation-driven mode="aspectj"/>

यदि आप 3.0 से पुराने संस्करण के साथ स्प्रिंग का उपयोग कर रहे हैं, तो आपको इसे अपने स्प्रिंग कॉन्फ़िगरेशन में भी जोड़ना होगा:

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>

जानकारी के लिए धन्यवाद। मैंने अभी के लिए कोड को वापस ले लिया, लेकिन क्या आप कृपया मुझे AspectJ का उपयोग करके एक उदाहरण भेज सकते हैं या मुझे कुछ उपयोगी लिंक प्रदान कर सकते हैं। अग्रिम में धन्यवाद। माइक।
माइक

मेरे जवाब में विशिष्ट विशिष्ट पहलू विन्यास जोड़ा गया। मुझे उम्मीद है यह मदद करेगा।
एस्पेन

10
अच्छी बात है! Btw: यह अच्छा होगा यदि आप मेरे प्रश्न को कुछ अंक देने के लिए सबसे अच्छे उत्तर के रूप में चिह्नित कर सकते हैं। (ग्रीन चेकमार्क)
एस्पेन

2
स्प्रिंग बूट config: @EnableTransactionManagement (मोड = AdviceMode.ASPECTJ)
VinyJones

64

यहाँ समस्या यह है, कि स्प्रिंग AOP परदे के पीछे नहीं है, बल्कि कॉल को इंटरसेप्ट करने के लिए अपनी सेवा का उदाहरण देते हैं। इसका प्रभाव यह होता है, कि आपकी सेवा आवृत्ति से "इस" किसी भी कॉल को सीधे उस उदाहरण पर लागू किया जाता है और रैपिंग प्रॉक्सी द्वारा अवरोधन नहीं किया जा सकता है (प्रॉक्सी को ऐसी किसी भी कॉल की जानकारी नहीं है)। एक समाधान पहले से ही उल्लेख किया गया है। बस एक और निफ्टी बस स्प्रिंग को सेवा में एक उदाहरण के रूप में इंजेक्ट करना होगा, और अपनी विधि को इंजेक्ट किए गए इंस्टेंस पर कॉल करें, जो आपके लेनदेन को संभालने वाला प्रॉक्सी होगा। लेकिन इस बात का ध्यान रखें कि इसके बुरे दुष्प्रभाव भी हो सकते हैं, यदि आपकी सेवा सेम एक सिंगलटन नहीं है:

<bean id="userService" class="your.package.UserService">
  <property name="self" ref="userService" />
    ...
</bean>

public class UserService {
    private UserService self;

    public void setSelf(UserService self) {
        this.self = self;
    }

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
        // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            self.addUser(user.getUserName, user.getPassword);
        }
    } 
}

3
यदि आप इस मार्ग पर जाने के लिए चुनते हैं (चाहे यह अच्छा डिज़ाइन है या नहीं यह एक और मामला है) और कंस्ट्रक्टर इंजेक्शन का उपयोग न करें, तो सुनिश्चित करें कि आप इस प्रश्न को
Jeshurun

क्या होगा अगर UserServiceसिंगलटन स्कोप है? यदि यह समान वस्तु है तो क्या होगा?
यान खोंस्की

26

स्प्रिंग 4 के साथ यह स्व ऑटोबायर्ड के लिए संभव है

@Service
@Transactional
public class UserServiceImpl implements UserService{
    @Autowired
    private  UserRepository repository;

    @Autowired
    private UserService userService;

    @Override
    public void update(int id){
       repository.findOne(id).setName("ddd");
    }

    @Override
    public void save(Users user) {
        repository.save(user);
        userService.update(1);
    }
}

2
सबसे बढ़िया उत्तर !! Thx
मजासनी

2
सही है अगर मैं गलत हूँ, लेकिन इस तरह के एक पैटर्न वास्तव में त्रुटि प्रवण है, हालांकि यह काम करता है। यह स्प्रिंग क्षमताओं के प्रदर्शन की तरह अधिक है, है ना? कोई व्यक्ति "इस बीन कॉल" व्यवहार से परिचित नहीं है, गलती से स्व-स्वतःभरण बीन को हटा सकता है (विधियां "इस" के माध्यम से हैं) आखिरकार जो समस्या का कारण हो सकता है जो पहली नज़र में पता लगाना मुश्किल है। इससे पहले कि यह पाया गया था यह भी पर्यावरण के वातावरण के लिए कर सकता है)।
पिदब्रो

2
@ पीडाब्रो आप सही कह रहे हैं, यह एक विशाल विरोधी पैटर्न है और यह पहली जगह में स्पष्ट नहीं है। इसलिए अगर आप कर सकते हैं तो आपको इससे बचना चाहिए। यदि आपको एक ही कक्षा की विधि का उपयोग करना है तो अधिक शक्तिशाली AOP पुस्तकालयों का उपयोग करने की कोशिश करें जैसे कि AspectJ
Almas Abdrazak

21

जावा 8 से शुरू होने की एक और संभावना है, जिसे मैं नीचे दिए गए कारणों से पसंद करता हूं:

@Service
public class UserService {

    @Autowired
    private TransactionHandler transactionHandler;

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            transactionHandler.runInTransaction(() -> addUser(user.getUsername, user.getPassword));
        }
    }

    private boolean addUser(String username, String password) {
        // TODO
    }
}

@Service
public class TransactionHandler {

    @Transactional(propagation = Propagation.REQUIRED)
    public <T> T runInTransaction(Supplier<T> supplier) {
        return supplier.get();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public <T> T runInNewTransaction(Supplier<T> supplier) {
        return supplier.get();
    }
}

इस दृष्टिकोण के निम्नलिखित फायदे हैं:

1) इसे निजी तरीकों पर लागू किया जा सकता है । तो आपको बस स्प्रिंग सीमाओं को पूरा करने के लिए एक विधि को सार्वजनिक करके एनकैप्सुलेशन को तोड़ना नहीं है।

2) एक ही विधि को विभिन्न लेनदेन प्रसार के भीतर कहा जा सकता है और यह उपयुक्त व्यक्ति को चुनने के लिए कॉलर पर निर्भर है । इन 2 लाइनों की तुलना करें:

transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));

3) यह स्पष्ट है, इस प्रकार अधिक पठनीय है।


यह भी खूब रही! यह उन सभी नुकसानों से बचता है जो वसंत ने अपने एनोटेशन के साथ पेश किए अन्यथा। इसे प्यार करना!
फ्रैंक हॉपकिंस

यदि मैं TransactionHandlerएक उपवर्ग के रूप में विस्तार करता हूं , और TransactionHandlerसुपर क्लास में इन दो तरीकों पर उपवर्ग कॉल करता है , तो क्या मैं अभी भी @Transactionalइच्छानुसार लाभ प्राप्त कर पाऊंगा ?
tom_mai78101

6

यह आत्म आह्वान के लिए मेरा समाधान है :

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void postContruct(){
        self = applicationContext.getBean(SBMWSBL.class);
    }

    // ...
}

0

आप उसी कक्षा के अंदर बीनफैक्टिंग को स्वत: प्राप्त कर सकते हैं और कर सकते हैं

getBean(YourClazz.class)

यह स्वचालित रूप से आपकी कक्षा को समीप करेगा और आपके @Transactional या अन्य अनूप एनोटेशन को ध्यान में रखेगा।


2
इसे एक बुरा अभ्यास माना जाता है। यहां तक ​​कि बीन को फिर से अपने आप में इंजेक्ट करना बेहतर है। GetBean (क्लैज़) का उपयोग करना आपके कोड के अंदर वसंत ApplicationContext कक्षाओं पर एक तंग युग्मन और मजबूत निर्भरता है। वर्ग द्वारा बीन प्राप्त करने से बीन को लपेटने के मामले में भी काम नहीं हो सकता है (वर्ग को बदला जा सकता है)।
वादिम किरिलचुक

0

मुद्दा यह है कि वसंत भार वर्ग और समीपता कैसे होती है। यह काम नहीं करेगा, जब तक आप अपने आंतरिक तरीके / लेनदेन को किसी अन्य कक्षा में नहीं लिखते हैं या अन्य वर्ग में नहीं जाते हैं और फिर फिर से अपनी कक्षा में आते हैं और फिर आंतरिक नेस्टेड ट्रांसकेशन विधि लिखते हैं।

सारांशित करने के लिए, स्प्रिंग प्रॉक्सिज़ उन परिदृश्यों की अनुमति नहीं देता है जो आप सामना कर रहे हैं। आपको दूसरी कक्षा में दूसरी लेनदेन विधि लिखनी होगी


0

यहाँ मैं एक ही वर्ग के भीतर विधि कॉल के केवल मामूली उपयोग के साथ छोटी परियोजनाओं के लिए क्या कर रहा हूँ। इन-कोड प्रलेखन की दृढ़ता से सलाह दी जाती है, क्योंकि यह सहकर्मियों को अजीब लग सकता है। लेकिन यह एकल के साथ काम करता है , परीक्षण करने में आसान है, सरल है, जल्दी से प्राप्त करने के लिए और मुझे पूर्ण विकसित एस्पेक्टेज इंस्ट्रूमेंटेशन बख्शा है। हालाँकि, अधिक भारी उपयोग के लिए मैं एस्पेक्टज समाधान की सलाह दूंगा जैसा कि एस्पेंस उत्तर में वर्णित है।

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {

    private final PersonDao _personDao;

    @Autowired
    public PersonDao(PersonDao personDao) {
        _personDao = personDao;
    }

    @Transactional
    public void addUser(String username, String password) {
        // call database layer
    }

    public void addUsers(List<User> users) {
        for (User user : users) {
            _personDao.addUser(user.getUserName, user.getPassword);
        }
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.