web-dev-qa-db-fra.com

La méthode Spring @Transaction, appelée par la méthode dans la même classe, ne fonctionne pas?

Je suis nouveau à Spring Transaction. Quelque chose que j'ai trouvé vraiment étrange, j'ai probablement compris cela correctement. Je voulais avoir un niveau de méthode transactionnel autour et j'ai une méthode caller dans la même classe et il semble que cela ne ressemble pas à ça, elle doit être appelée depuis une classe séparée. Je ne comprends pas comment c'est possible. Si quelqu'un avait une idée sur la façon de résoudre ce problème, je l'apprécierais grandement. Je voudrais utiliser la même classe pour appeler la méthode transactionnelle annotée.

Voici le code: 

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

C'est une limitation de Spring AOP (objets dynamiques et cglib ).

Si vous configurez Spring pour utiliser AspectJ pour gérer les transactions, votre code fonctionnera.

La solution de rechange simple et probablement la meilleure consiste à refactoriser votre code. Par exemple, une classe qui gère les utilisateurs et une autre qui traite chaque utilisateur. Ensuite, la gestion des transactions default avec Spring AOP fonctionnera.


Conseils de configuration pour la gestion des transactions avec AspectJ

Pour que Spring puisse utiliser AspectJ pour les transactions, vous devez définir le mode sur AspectJ:

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

Si vous utilisez Spring avec une version plus ancienne que la version 3.0, vous devez également ajouter ceci à votre configuration Spring:

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

Le problème ici est que les mandataires AOP de Spring ne s'étendent pas, mais enveloppent plutôt votre instance de service pour intercepter les appels. Cela a pour effet que tout appel à "this" depuis votre instance de service est directement appelé sur cette instance et ne peut pas être intercepté par le proxy enveloppant (le proxy n'est même pas au courant de cet appel). Une solution est déjà mentionnée. Une autre solution intéressante serait simplement que Spring injecte une instance du service dans le service lui-même et appelle votre méthode sur l'instance injectée, qui sera le proxy qui gérera vos transactions. Mais sachez que cela peut aussi avoir de mauvais effets secondaires si votre bean service n’est pas un singleton:

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

Ceci est ma solution pour auto invocation :

private SBMWSBL self;

@Autowired
private ApplicationContext applicationContext;

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

Avec Spring 4, il est possible de s'auto-câbler

@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);
    }
}
5
Almas Abdrazak

À partir de Java 8, il existe une autre possibilité, que je préfère pour les raisons suivantes:

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

Cette approche présente les avantages suivants:

1) Il peut être appliqué à privé méthodes. Il n'est donc pas nécessaire de rompre l'encapsulation en rendant publique une méthode simplement pour satisfaire aux limites de Spring.

2) La même méthode peut être appelée dans le cadre d’une propagation de transaction différente et l’appelant est libre pour choisir celle qui convient. Comparez ces 2 lignes:

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

3) C'est explicite, donc plus lisible.

4
Bunarro

La question est liée à la façon dont les classes de charge de ressort et les mandataires. Cela ne fonctionnera pas, jusqu'à ce que vous écriviez votre méthode/transaction interne dans une autre classe ou que vous alliez dans une autre classe, puis revenez dans votre classe et écrivez ensuite la méthode de transcoration imbriquée interne.

Pour résumer, les proxy de printemps n'autorisent pas les scénarios auxquels vous êtes confrontés. vous devez écrire la deuxième méthode de transaction dans une autre classe

0
Ujjwal Choudhari