Spring @Transaction metodu aynı sınıftaki metot tarafından çağrı, çalışmıyor mu?


110

Spring Transaction'da yeniyim. Gerçekten tuhaf bulduğum bir şey, muhtemelen bunu doğru anladım.

Yöntem düzeyinde bir işlem yapmak istedim ve aynı sınıf içinde bir çağıran yöntemim var ve bundan hoşlanmıyor gibi görünüyor, ayrı bir sınıftan çağrılması gerekiyor. Bunun nasıl mümkün olduğunu anlamıyorum.

Herhangi birinin bu sorunu nasıl çözeceğine dair bir fikri varsa, çok memnun olurum. Açıklamalı işlem yöntemini çağırmak için aynı sınıfı kullanmak istiyorum.

İşte kod:

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

TransactionTemplateYaklaşıma bir göz atın : stackoverflow.com/a/52989925/355438
Lu55

Kendi kendine çağırmanın neden çalışmadığı hakkında, bkz. 8.6 Proxy mekanizmaları .
Jason Law

Yanıtlar:


99

Spring AOP'nin (dinamik nesneler ve cglib ) bir sınırlamasıdır .

Spring'i işlemleri gerçekleştirmek için AspectJ'yi kullanacak şekilde yapılandırırsanız , kodunuz çalışacaktır.

Basit ve muhtemelen en iyi alternatif, kodunuzu yeniden düzenlemektir. Örneğin, kullanıcıları yöneten bir sınıf ve her kullanıcıyı işleyen bir sınıf. Daha sonra Spring AOP ile varsayılan işlem işleme çalışacaktır.


AspectJ ile işlemleri yönetmek için yapılandırma ipuçları

Spring'in işlemler için AspectJ'yi kullanmasını sağlamak için modu AspectJ olarak ayarlamanız gerekir:

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

Spring'i 3.0'dan daha eski bir sürümle kullanıyorsanız, bunu Spring konfigürasyonunuza da eklemeniz gerekir:

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

Bilgi için teşekkürler. Şimdilik kodu yeniden düzenledim, ancak lütfen AspectJ'yi kullanarak bir örnek gönderebilir veya bana bazı yararlı bağlantılar sağlayabilir misiniz? Şimdiden teşekkürler. Mike.
Mike

Cevabıma işleme özel AspectJ yapılandırması eklendi. Umut ediyorum bu yardım eder.
Espen

10
Bu iyi! Btw: Sorumu en iyi cevap olarak işaretlerseniz bana puan verirseniz çok iyi olur. (yeşil onay işareti)
Espen

2
Spring boot yapılandırması: @EnableTransactionManagement (mode = AdviceMode.ASPECTJ)
VinyJones

64

Buradaki sorun, Spring'in AOP proxy'lerinin genişlememesi, hizmet örneğinizi çağrıları durdurmak için sarmalamasıdır. Bu, hizmet örneğinizden "this" e yapılan herhangi bir çağrının doğrudan bu örnekte çağrılması ve sarmalayıcı proxy tarafından durdurulamaması (proxy böyle bir çağrının farkında bile değildir) etkisine sahiptir. Bir çözümden zaten bahsediliyor. Bir başka şık olan ise, Spring'in hizmetin bir örneğini hizmetin kendisine enjekte etmesini sağlamak ve işlemlerinizi yöneten proxy olan enjekte edilen örnekte yönteminizi çağırmaktır. Ancak servis fasulyeniz tekli değilse, bunun da kötü yan etkileri olabileceğini unutmayın:

<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
Bu rotaya gitmeyi seçerseniz (bu iyi bir tasarım olsun ya da olmasın başka bir mesele) ve kurucu enjeksiyonu kullanmazsanız, bu soruyu
Jeshurun

Ya UserServicesingleton kapsamı varsa ? Ya aynı nesneyse?
Yan Khonski

26

Spring 4 ile Otomatik Kablolama yapmak mümkündür

@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
EN İYİ CEVAP !! Thx
mjassani

2
Yanılıyorsam düzeltin ama böyle bir kalıp işe yarasa da gerçekten hataya açıktır. Daha çok Spring yeteneklerinin bir vitrini gibi, değil mi? "Bu fasulye çağrısı" davranışına aşina olmayan biri yanlışlıkla kendi kendine bağlanan fasulyeyi kaldırabilir (sonuçta yöntemler "bu" aracılığıyla kullanılabilir), bu da ilk bakışta tespit edilmesi zor olan sorunlara neden olabilir. Hatta bulunmadan önce üretim ortamına bile ulaşabilir).
pidabrow

2
@pidabrow haklısın, bu büyük bir anti model ve ilk etapta açık değil. Yani eğer yapabilirsen bundan kaçınmalısın. Aynı sınıfın yöntemini kullanmanız gerekiyorsa, AspectJ
Almas Abdrazak

21

Java 8'den başlayarak, aşağıda verilen nedenlerden dolayı tercih ettiğim başka bir olasılık var:

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

Bu yaklaşım aşağıdaki avantajlara sahiptir:

1) Özel yöntemlere uygulanabilir . Dolayısıyla, yalnızca Spring sınırlamalarını karşılamak için bir yöntemi halka açık hale getirerek kapsüllemeyi kırmanız gerekmez.

2) Farklı işlem yayılımlarında aynı yöntem çağrılabilir ve uygun olanı seçmek arayan kişiye bağlıdır . Bu 2 satırı karşılaştırın:

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

3) Açıktır, dolayısıyla daha okunabilirdir.


Bu harika! Aksi takdirde, Bahar'ın ek açıklamasıyla getirdiği tüm tuzakları önler. Sevdim!
Frank Hopkins

TransactionHandlerBir alt sınıf olarak genişletirsem ve alt TransactionHandlersınıf, süper sınıfta bu iki yöntemi çağırırsa, yine de @Transactionalamaçlandığı gibi faydaları elde edebilecek miyim ?
tom_mai78101

6

Bu, kendi kendine çağrı için benim çözümüm :

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}

0

BeanFactory'yi aynı sınıf içinde otomatik olarak bağlayabilir ve

getBean(YourClazz.class)

Sınıfınıza otomatik olarak proxy uygular ve @Transactional veya diğer aop ek açıklamanızı dikkate alır.


2
Kötü bir uygulama olarak kabul edilir. Fasulyeyi özyinelemeli olarak kendi içine enjekte etmek bile daha iyidir. GetBean (clazz) kullanmak sıkı bir bağlantı ve kodunuzun içindeki bahar ApplicationContext sınıflarına güçlü bir bağımlılıktır. Ayrıca fasulyenin yayla sarılması durumunda sınıfa göre fasulye almak işe yaramayabilir (sınıf değiştirilebilir).
Vadim Kirilchuk

0

Sorun, yay yükü sınıflarının ve proxy'lerin nasıl olduğu ile ilgilidir. İçsel yönteminizi / işleminizi başka bir sınıfa yazana veya başka bir sınıfa gidene ve sonra tekrar sınıfınıza gelene ve ardından iç içe geçmiş transcation yöntemini yazana kadar çalışmayacaktır.

Özetlemek gerekirse, bahar vekilleri karşı karşıya olduğunuz senaryolara izin vermez. 2. işlem yöntemini diğer sınıfta yazmanız gerekir


0

İşte aynı sınıf içinde yöntem çağrılarının sadece marjinal kullanımı olan küçük projeler için yaptığım şey. Meslektaşlara tuhaf görünebileceği için kod içi dokümantasyon şiddetle tavsiye edilir. Ancak tekli tonlarla çalışıyor , test etmesi kolay, basit, elde etmesi hızlı ve tam gelişmiş AspectJ enstrümantasyonunu bana veriyor. Ancak, daha yoğun kullanım için Espens yanıtında açıklandığı gibi AspectJ çözümünü tavsiye ederim.

@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);
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.