Spring@Transaction方法调用同一类内的方法,行不行?

ifmq2ha2  于 2022-09-18  发布在  Spring
关注(0)|答案(9)|浏览(218)

我对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);
        }
    } 
}
mefy6pfw

mefy6pfw1#

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

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

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

使用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>
zysjyyx4

zysjyyx42#

在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的限制而通过使方法成为公共方法来打破封装。
1.相同的方法可以在不同的事务传播中调用,由调用者自行选择。比较这两行:

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

TransactionHandler.runInNewTransaction(()->userService.addUser(user.getUserName,user.getPassword));
1.条理清晰,可读性更强。

nnsrf1az

nnsrf1az3#

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

c9qzyr3d4#

有了Spring4,就有可能实现自连

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

ahy6op9u5#

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

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}
fkvaft9z

fkvaft9z6#

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

getBean(YourClazz.class)

它将自动代理您的类,并考虑您的@Transaction或其他AOP注解。

hfyxw5xn

hfyxw5xn7#

下面是我为在同一个类中只使用少量方法调用的小项目所做的工作。强烈建议编写代码中的文档,因为它对同事来说可能看起来很奇怪。但是它可以与单例一起使用**,它易于测试、简单、快速实现,并且省去了成熟的AspectJ工具。但是,对于更频繁的使用,我建议使用Espens Answer中描述的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);
        }
    }
}
x7rlezfr

x7rlezfr8#

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

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

acruukt9

acruukt99#

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

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

相关问题