Spring @Transaction方法通过同一个类内的方法调用,不起作用?

发布于 2024-09-13 21:39:35 字数 702 浏览 6 评论 0原文

我是 Spring 事务的新手。我觉得很奇怪,也许我确实正确理解了这一点。

我想在方法级别进行事务处理,并且我在同一个类中有一个调用者方法,但它似乎不喜欢那样,必须从单独的类中调用它。我不明白这怎么可能。

如果有人知道如何解决这个问题,我将不胜感激。我想使用同一个类来调用带注释的事务方法。

这是代码:

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

I am new to Spring Transaction. Something that I found really odd, probably I did understand this properly.

I wanted to have a transactional around method level and I have a caller method within the same class and it seems like it does not like that, it has to be called from the separate class. I don't understand how is that possible.

If anyone has an idea how to resolve this issue, I would greatly appreciate. I would like to use the same class to call the annotated transactional method.

Here is the 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);
        }
    } 
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

泪痕残 2024-09-20 21:39:35

这是 Spring AOP 的限制(动态对象和 cglib)。

如果您将 Spring 配置为使用 AspectJ 来处理事务,您的代码将正常工作。

简单且可能是最好的替代方法是重构代码。例如,一个处理用户的类和一个处理每个用户的类。然后 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>

It's a limitation of Spring AOP (dynamic objects and cglib).

If you configure Spring to use AspectJ to handle the transactions, your code will work.

The simple and probably best alternative is to refactor your code. For example one class that handles users and one that process each user. Then default transaction handling with Spring AOP will work.


Configuration tips for handling transactions with AspectJ

To enable Spring to use AspectJ for transactions, you must set the mode to AspectJ:

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

If you're using Spring with an older version than 3.0, you must also add this to your Spring configuration:

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>
温柔一刀 2024-09-20 21:39:35

在 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 call userRepository
    }
}

@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. 可以在不同的事务传播中调用相同的方法,由调用者选择合适的方法。比较这两行:

     transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
    
     transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
    
  3. 它是明确的,因此更具可读性。

In Java 8+ there's a possibility, which I prefer for the reasons given below:

@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 call userRepository
    }
}

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

This approach has the following advantages:

  1. It may be applied to private methods. So you don't have to break encapsulation by making a method public just to satisfy Spring limitations.

  2. Same method may be called within different transaction propagations and it is up to the caller to choose the suitable one. Compare these 2 lines:

     transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
    
     transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
    
  3. It is explicit, thus more readable.

毁我热情 2024-09-20 21:39:35

这里的问题是,Spring 的 AOP 代理不会扩展,而是包装您的服务实例来拦截调用。这样做的效果是,从服务实例内对“this”的任何调用都会直接在该实例上调用,并且不能被包装代理拦截(代理甚至不知道任何此类调用)。已经提到了一种解决方案。另一个巧妙的方法是简单地让 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);
        }
    } 
}

The problem here is, that Spring's AOP proxies don't extend but rather wrap your service instance to intercept calls. This has the effect, that any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call). One solutions is already mentioned. Another nifty one would be to simply have Spring inject an instance of the service into the service itself, and call your method on the injected instance, which will be the proxy that handles your transactions. But be aware, that this may have bad side effects too, if your service bean is not a 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);
        }
    } 
}
忱杏 2024-09-20 21:39:35

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

With Spring 4 it's possible to Self autowired

@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);
    }
}
稀香 2024-09-20 21:39:35

这是我的自调用解决方案:

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}

This is my solution for self invocation:

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}
墟烟 2024-09-20 21:39:35

您可以在同一个类中自动装配 BeanFactory 并执行

getBean(YourClazz.class)

它会自动代理您的类并考虑您的 @Transactional 或其他 aop 注释。

You can autowired BeanFactory inside the same class and do a

getBean(YourClazz.class)

It will automatically proxify your class and take into account your @Transactional or other aop annotation.

追星践月 2024-09-20 21:39:35

以下是我对同一类中仅少量使用方法调用的小型项目所做的操作。强烈建议使用代码内文档,因为这对同事来说可能看起来很奇怪。但是它可以与单例一起使用,易于测试、简单、快速实现,并且使我无需使用完整的 AspectJ 工具。然而,对于更频繁的使用,我建议使用 AspectJ 解决方案,如 Espens 答案中所述。

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

Here is what I do for small projects with only marginal usage of method calls within the same class. In-code documentation is strongly advised, as it may look strange to colleagues. But it works with singletons, is easy to test, simple, quick to achieve and spares me the full blown AspectJ instrumentation. However, for more heavy usage I'd advice the AspectJ solution as described in Espens answer.

@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);
        }
    }
}
∞梦里开花 2024-09-20 21:39:35

使用 Spring 2.5.15 进行测试,您使用 @Resource 进行自调用。请参阅下面的测试类示例

@Service
@RequiredArgsConstructor
public class Example {
   private final EntityManager entityManager;
   
   @Resource
   private Example self;


   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void testSelf(){
      self.transaction();
   }


   @Transactional(propagation = Propagation.SUPPORTS)
   public void transaction(){
       entityManager.persist(new MyEntity(28L));
   }

@SpringBootTest
class ExampleTest {
    @Autowired
    private Example example;
    @Autowired
    private EntityManager entityManager;

    @Test
    void testSelf(){
        transactionHandler.testSelf();
        assertNotNull(entityManager.find(Estado.class, 28L));
    }
}

Tested with Spring 2.5.15, you do a self invocation using @Resource. See the example bellow

@Service
@RequiredArgsConstructor
public class Example {
   private final EntityManager entityManager;
   
   @Resource
   private Example self;


   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void testSelf(){
      self.transaction();
   }


   @Transactional(propagation = Propagation.SUPPORTS)
   public void transaction(){
       entityManager.persist(new MyEntity(28L));
   }

Test class:

@SpringBootTest
class ExampleTest {
    @Autowired
    private Example example;
    @Autowired
    private EntityManager entityManager;

    @Test
    void testSelf(){
        transactionHandler.testSelf();
        assertNotNull(entityManager.find(Estado.class, 28L));
    }
}
注定孤独终老 2024-09-20 21:39:35

该问题与 spring 如何加载类和代理有关。除非您在另一个类中编写内部方法/事务,或者转到其他类,然后再次来到您的类,然后编写内部嵌套事务方法,否则它将不起作用。

总而言之,Spring 代理不允许您面临的场景。你必须在其他类中编写第二个事务方法

The issue is related to how spring load classes and proxies. It will not work , untill you write your inner method / transaction in another class or go to other class and then again come to your class and then write the inner nested transcation method.

To summarize, spring proxies does not allow the scenarios which you are facing. you have to write the 2nd transaction method in other class

爱的那么颓废 2024-09-20 21:39:35

只需在服务顶部添加以下注释

@Scope(proxyMode = TARGET_CLASS)

just add following annotation on top of the service

@Scope(proxyMode = TARGET_CLASS)
残花月 2024-09-20 21:39:35

使用 AspectJ 或其他方式是没有意义的。只需使用 AOP 就足够了。因此,我们可以在 addUsers(Listusers) 中添加 @Transactional 来解决当前问题。

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

        }
    }

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

There is no point to use AspectJ or Other ways. Just using AOP is sufficient. So, we can add @Transactional to addUsers(List<User> users) to solve current issue.

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

        }
    }

    @Transactional
    public boolean addUsers(List<User> users) {
        for (User user : users) {
            addUser(user.getUserName, user.getPassword);
        }
    } 
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文