由同一个类中的方法调用 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);
}
}
}
108312 次浏览

这是 Spring AOP(动态对象和 < em > cglib )的一个限制。

如果您将 Spring 配置为使用 AspectJ来处理事务,那么您的代码就可以工作了。

最简单也可能是最好的替代方法是重构代码。例如,一个类处理用户,另一个类处理每个用户。然后使用 SpringAOP 进行 违约事务处理。


用 AspectJ 处理事务的配置提示

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

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

如果您使用的是比3.0版本更老的 Spring,那么您还必须将其添加到您的 Spring 配置中:

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

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

您可以在同一个类中自动连接 BeanFactory 并执行

getBean(YourClazz.class)

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

这是我对 自我祈祷的解决方案:

public class SBMWSBL {
private SBMWSBL self;


@Autowired
private ApplicationContext applicationContext;


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


// ...
}

有了 Spring 4,Self 就可以自动连接了

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

这个问题与弹簧负载类和代理的方式有关。它不会工作,直到您在另一个类中编写内部方法/事务,或者转到其他类,然后再次来到您的类,然后编写内部嵌套的转换方法。

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

在 Java8 + 中还有另一种可能性,我更喜欢这种可能性,原因如下:

@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. 在不同的事务传播和 这取决于打电话的人中可以调用相同的方法来选择合适的方法:

    RunInTransaction (()-> userService.addUser (user.getUserName,user.getPassword)) ;

    RunInNewTransaction (()-> userService.addUser (user.getUserName,user.getPassword)) ;

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

下面是我对在同一个类中只有少量方法调用的小型项目所做的工作。强烈建议使用代码内文档,因为它对同事来说可能看起来很奇怪。但是 它适用于单身人士,易于测试,简单,快速实现,省去了我全面成熟的 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);
}
}
}

没有必要使用 AspectJ 或其他方式。仅仅使用 AOP 就足够了。因此,我们可以将@Transactional 添加到 addUsers(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);
}
}
}