私は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);
}
}
}
Spring AOP (動的オブジェクトおよび cglib )の制限です。
AspectJ を使用してトランザクションを処理するようにSpringを構成すると、コードは機能します。
シンプルでおそらく最良の代替手段は、コードをリファクタリングすることです。たとえば、ユーザーを処理するクラスと、各ユーザーを処理するクラス。その後、defaultSpring AOPでのトランザクション処理が機能します。
SpringでトランザクションにAspectJを使用できるようにするには、モードをAspectJに設定する必要があります。
<tx:annotation-driven mode="aspectj"/>
3.0より古いバージョンでSpringを使用している場合、これもSpring構成に追加する必要があります。
<bean class="org.springframework.transaction.aspectj
.AnnotationTransactionAspect" factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>
ここでの問題は、SpringのAOPプロキシが拡張されず、サービスインスタンスをラップして呼び出しをインターセプトすることです。これには、サービスインスタンス内からの「this」の呼び出しがそのインスタンスで直接呼び出され、ラッピングプロキシによってインターセプトできないという効果があります(プロキシはそのような呼び出しを認識しません)。 1つの解決策がすでに述べられています。もう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);
}
}
}
Spring 4では、自動配線が可能です
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserRepositroy repositroy;
@Autowired
private UserService userService;
@Override
public void update(int id){
repository.findOne(id).setName("ddd");
}
@Override
public void save(Users user) {
repositroy.save(user);
userService.update(1);
}
}
これが自己呼び出しの私の解決策です。
public class SBMWSBL {
private SBMWSBL self;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void postContruct(){
self = applicationContext.getBean(SBMWSBL.class);
}
// ...
}
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)privateメソッドに適用できます。したがって、Springの制限を満たすためだけにメソッドをパブリックにすることで、カプセル化を解除する必要はありません。
2)異なるトランザクション伝播内で同じメソッドを呼び出すことができ、適切なものを選択するために呼び出し元次第が呼び出されます。これらの2行を比較します。
transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
3)明示的であるため、読みやすくなります。
この問題は、スプリングがクラスとプロキシをロードする方法に関連しています。内部メソッド/トランザクションを別のクラスに書き込むか、別のクラスに移動してから再びクラスにアクセスして、内部のネストされたトランザクションメソッドを記述するまで、機能しません。
要約すると、スプリングプロキシは、直面しているシナリオを許可しません。あなたは他のクラスで2番目のトランザクションメソッドを書く必要があります
同じクラス内でBeanFactoryを自動配線して、
getBean(YourClazz.class)
クラスを自動的にプロキシ化し、@ Transactionalまたは他のaopアノテーションを考慮します。