Spring @Transaction方法由同一类中的方法调用,不起作用吗?


108

我是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代理机制
詹森·罗

Answers:


99

这是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>

感谢您的信息。我现在重构了代码,但是能否请您使用AspectJ给我发送示例或提供一些有用的链接。提前致谢。麦克风。
迈克(Mike)2010年

在我的答案中添加了特定于事务的AspectJ配置。希望对您有所帮助。
Espen

10
非常好!顺便说一句:如果您可以将我的问题标记为给我一些观点的最佳答案,那就太好了。(绿色复选标记)
Espen

2
春季启动配置:@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
VinyJones

64

这里的问题是,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);
        }
    } 
}

3
如果您确实选择了这条路线(这是否是好的设计是另一回事)并且不使用构造函数注入,请确保您也看到此问题
Jeshurun 2012年

如果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功能的展示,对吗?某些不熟悉“ this bean call”行为的人可能会不小心删除自动装配的bean(毕竟这些方法可通过“ this。”获得),这可能会导致难以一眼发现的问题。它甚至可以在发现之前进入产品环境。
pidabrow

2
@pidabrow,您是对的,它是一个巨大的反模式,一开始它并不明显。因此,如果可以的话,应该避免使用它。如果必须使用同一类的方法,则尝试使用功能更强大的AOP库,例如AspectJ
Almas Abdrazak

20

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

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

3)它是显式的,因此更具可读性。


这很棒!否则,它避免了Spring通过其注释引入的所有陷阱。爱它!
弗兰克·霍普金斯

如果我扩展TransactionHandler为子类,并且子类在TransactionHandler超类中调用了这两个方法,那么我是否仍然可以@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)紧密耦合并强烈依赖于代码内部的Spring ApplicationContext类。同样,在春季包装bean的情况下,按类获取bean可能不起作用(可以更改类)。
Vadim Kirilchuk

0

问题与弹簧载荷等级和代理如何相关。直到您在另一个类中编写内部方法/事务或转到其他类,然后再次进入您的类,然后编写内部嵌套的事务处理方法,该方法才起作用。

总而言之,Spring Proxy不允许您面对的情况。您必须在其他类中编写第二种交易方法


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);
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.