154

我是 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);
        }
    } 
}
4

9 回答 9

116

这是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>
于 2010-08-07T08:36:05.500 回答
68

这里的问题是,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);
        }
    } 
}
于 2010-10-05T20:41:57.113 回答
64

在 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. 它是明确的,因此更具可读性。

于 2019-05-27T13:25:18.893 回答
36

使用 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);
    }
}
于 2018-03-19T05:35:52.683 回答
7

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

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}
于 2016-12-04T03:53:42.383 回答
1

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

getBean(YourClazz.class)

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

于 2014-11-04T17:08:47.923 回答
1

这是我为在同一个类中仅少量使用方法调用的小型项目所做的。强烈建议使用代码内文档,因为它对同事来说可能看起来很奇怪。但它适用于单例,易于测试、简单、快速实现,并且为我节省了完整的 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);
        }
    }
}
于 2020-04-22T09:15:50.393 回答
0

使用 AspectJ 或其他方式没有意义。只需使用 AOP 就足够了。因此,我们可以添加 @TransactionaladdUsers(List<User> users)来解决当前问题。

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);
        }
    } 
}
于 2021-06-23T15:50:53.710 回答
0

该问题与弹簧加载类和代理的方式有关。它不会起作用,直到你在另一个类中编写你的内部方法/事务或者去另一个类然后再次来到你的类然后编写内部嵌套事务方法。

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

于 2018-07-06T10:14:03.440 回答