同じクラス内のメソッドによるSpring @Transactionメソッド呼び出し、機能しませんか?


109

Spring Transactionは初めてです。私が本当に奇妙だと思った何か、おそらく私はこれを正しく理解しました。

メソッドレベルのトランザクションを使用したいと思っていて、同じクラス内に呼び出し側メソッドがあり、それが好きではないようです。別のクラスから呼び出す必要があります。どうしてそれが可能か分かりません。

誰かがこの問題を解決する方法を知っているなら、私は非常に感謝します。同じクラスを使用して、注釈付きトランザクションメソッドを呼び出したいと思います。

これがコードです:

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プロキシメカニズム」を参照してください。
Jason Law、

回答:


99

これは、Spring AOP(動的オブジェクトおよびcglib)の制限事項です。

AspectJを使用してトランザクションを処理するようにSpringを構成すると、コードは機能します。

シンプルでおそらく最良の代替策は、コードをリファクタリングすることです。たとえば、ユーザーを処理するクラスと、各ユーザーを処理するクラスです。その後、Spring AOPでのデフォルトのトランザクション処理が機能します。


AspectJでトランザクションを処理するための構成のヒント

SpringがトランザクションにAspectJを使用できるようにするには、モードをAspectJに設定する必要があります。

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

Springを3.0より古いバージョンで使用している場合は、これをSpring構成に追加する必要もあります。

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

情報ありがとうございました。とりあえずコードをリファクタリングしましたが、AspectJを使用して例を送ったり、役立つリンクを提供してくれませんか。前もって感謝します。マイク。
Mike

トランザクション固有のAspectJ構成を私の回答に追加しました。お役に立てば幸いです。
Espen

10
それは良い!ところで、私の質問をベストアンサーとしてマークして、いくつかのポイントを付けていただければ幸いです。(緑色のチェックマーク)
エスペン

2
春のブート構成:@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
VinyJones

64

ここでの問題は、SpringのAOPプロキシは拡張されず、サービスインスタンスをラップして呼び出しをインターセプトすることです。これには、サービスインスタンス内から「this」への呼び出しがそのインスタンスで直接呼び出され、ラッピングプロキシによってインターセプトできないという効果があります(プロキシはそのような呼び出しを認識していません)。1つの解決策はすでに述べられています。別の気の利いたものは、単にSpringがサービス自体にサービスのインスタンスを注入し、注入されたインスタンスでメソッドを呼び出すことです。これは、トランザクションを処理するプロキシになります。ただし、サービスBeanがシングルトンではない場合、これも悪影響をもたらす可能性があることに注意してください。

<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シングルトンスコープがある場合はどうなりますか?同じオブジェクトの場合はどうなりますか?
Yan Khonski、

26

Spring 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
mjassani

2
私が間違っている場合は修正してください。ただし、このようなパターンは機能しますが、実際にはエラーが発生しやすくなります。それは、Spring機能のショーケースのようなものですよね?「このBean呼び出し」の動作に詳しくない人が誤って自己自動接続Beanを削除する可能性があり(メソッドは「this。」から使用できるため)、一見して検出が難しい問題を引き起こす可能性があります。それが見つかる前に、本番環境に移行することもできます)。
pidabrow

2
@pidabrowあなたは正しい、それは巨大な反パターンであり、そもそも明らかではない。だからできればそれを避けるべきです。同じクラスのメソッドを使用する必要がある場合は、AspectJなどのより強力なAOPライブラリを使用してみてください
Almas Abdrazak

21

Java 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)プライベートメソッドに適用できます。したがって、Springの制限を満たすためだけにメソッドを公開してカプセル化を解除する必要はありません。

2)異なるトランザクション伝播内で同じメソッドが呼び出される場合があり、適切なメソッドを選択するのは呼び出し側の責任です。次の2行を比較します。

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

3)明示的であるため、読みやすくなります。


これは素晴らしい!それ以外の場合は、Springがアノテーションで導入するすべての落とし穴を回避します。大好きです!
フランクホプキンス

TransactionHandlerサブクラスとして拡張し、サブクラスがTransactionHandlerスーパークラスのこれら2つのメソッドを呼び出す場合でも@Transactional、意図したとおりの利点を得ることができますか?
tom_mai78101

6

これは自己呼び出しのための私の解決策です:

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}

0

同じクラス内でBeanFactoryを自動配線して、

getBean(YourClazz.class)

これは自動的にクラスをプロキシ化し、@ Transactionalまたは他のaopアノテーションを考慮に入れます。


2
これは悪い習慣と見なされています。Beanを再帰的にそれ自体に注入することもできます。getBean(clazz)の使用は、コード内のスプリングApplicationContextクラスへの密接な結合と強い依存性です。また、Beanのスプリングラッピングの場合、クラスによるBeanの取得は機能しない可能性があります(クラスが変更される可能性があります)。
Vadim Kirilchuk 2015

0

この問題は、スプリング荷重のクラスとプロキシに関連しています。別のクラスで内部メソッド/トランザクションを作成するか、別のクラスに移動してから再びクラスに移動して、内部のネストされたトランザクションメソッドを作成するまでは、機能しません。

要約すると、春のプロキシはあなたが直面しているシナリオを許可しません。あなたは他のクラスで2番目のトランザクションメソッドを書く必要があります


0

これは、同じクラス内でメソッド呼び出しをほとんど使用しない小規模なプロジェクトで私が行うことです。同僚にとっては奇妙に見えるかもしれないので、コード内のドキュメントを強くお勧めします。しかし、それはシングルトンで動作し、テストが簡単で、シンプルで、迅速に達成でき、本格的なAspectJインストゥルメンテーションを節約できます。ただし、より頻繁に使用する場合は、Espensの回答で説明されているAspectJソリューションをお勧めします。

@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);
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.