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

Mik*_*ike 96 java spring aspectj spring-aop

我是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);
        }
    } 
}
Run Code Online (Sandbox Code Playgroud)

Esp*_*pen 89

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

如果您将Spring配置为使用AspectJ来处理事务,那么您的代码将起作用.

简单且可能最好的替代方法是重构代码.例如,一个处理用户的类和一个处理每个用户的类.然后使用Spring AOP进行默认事务处理将起作用.


使用AspectJ处理事务的配置提示

要使Spring能够将AspectJ用于事务,必须将模式设置为AspectJ:

<tx:annotation-driven mode="aspectj"/>
Run Code Online (Sandbox Code Playgroud)

如果您使用的是旧版本而不是3.0的Spring,则还必须将其添加到Spring配置中:

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>
Run Code Online (Sandbox Code Playgroud)

  • 非常好!顺便说一句:如果你能把我的问题标记为给我一些积分的最佳答案,那就太好了.(绿色复选标记) (10认同)
  • Spring启动配置:@EnableTransactionManagement(mode = AdviceMode.ASPECTJ) (6认同)

小智 58

这里的问题是,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);
        }
    } 
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你选择走这条路线(这是否是好的设计是另一回事)并且不使用构造函数注入,请确保你也看到[这个问题](http://stackoverflow.com/questions/5152686/自喷射用弹簧) (3认同)

Alm*_*zak 18

使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 最佳答案 !!谢谢 (2认同)
  • 如果我错了,请纠正我,但这种模式确实很容易出错,尽管它有效。它更像是 Spring 功能的展示,对吗?不熟悉“this bean 调用”行为的人可能会意外删除自自动装配的 bean(毕竟可以通过“this.”使用这些方法),这可能会导致乍一看很难发现的问题。它甚至可以在被发现之前就进入生产环境)。 (2认同)
  • @pidabrow,你是对的,这是一个巨大的反模式,而且一开始并不明显。所以如果可以的话你应该避免它。如果必须使用同一类的方法,请尝试使用更强大的 AOP 库,例如 AspectJ (2认同)

Bun*_*rro 9

从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();
    }
}
Run Code Online (Sandbox Code Playgroud)

此方法具有以下优点:

1)它可以应用于私有方法。因此,您不必为了满足Spring的限制而通过公开方法来破坏封装。

2)可以在不同的事务传播中调用相同的方法,并且由调用方选择合适的方法。比较以下两行:

transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
Run Code Online (Sandbox Code Playgroud)

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

  • 这很棒!它避免了 Spring 通过注释引入的所有陷阱。爱它! (5认同)
  • @burebista,你做错了,可以定义两个具有相同名称的方法,其中一个接受供应商并返回 T,另一个接受 runnable 并返回 void。 (3认同)
  • 听起来太棒了!我想知道是否有一些注意事项? (2认同)

Hle*_*lex 6

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

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}
Run Code Online (Sandbox Code Playgroud)