我是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);
}
}
}
答案 0 :(得分:84)
这是Spring AOP(动态对象和cglib)的限制。
如果将Spring配置为使用AspectJ来处理事务,则代码将起作用。
简单且可能最好的替代方法是重构代码。例如,一个处理用户的类和一个处理每个用户的类。然后使用Spring AOP的默认事务处理将起作用。
要使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>
答案 1 :(得分:53)
这里的问题是,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);
}
}
}
答案 2 :(得分:9)
使用Spring 4,可以进行Self autowired
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserRepositroy repositroy;
@Autowired
private UserService userService;
@Override
public void update(int id){
repository.findOne(id).setName("ddd");
}
@Override
public void save(Users user) {
repositroy.save(user);
userService.update(1);
}
}
答案 3 :(得分:5)
这是自我调用的解决方案:
public class SBMWSBL {
private SBMWSBL self;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void postContruct(){
self = applicationContext.getBean(SBMWSBL.class);
}
// ...
}
答案 4 :(得分:1)
从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)它是显式的,因此更具可读性。
答案 5 :(得分:0)
您可以在同一个类中自动装配BeanFactory并执行
getBean(YourClazz.class)
它会自动代理您的课程并考虑您的@Transactional或其他aop注释。
答案 6 :(得分:0)
该问题与弹簧载荷的类别和代理方式有关。直到您在另一个类中编写内部方法/事务或转到另一个类,然后再次进入您的类然后编写内部嵌套的事务处理方法,该方法才能起作用。
总而言之,Spring Proxy不允许您面对所面临的情况。您必须在其他类中编写第二种交易方法
答案 7 :(得分: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);
}
}
}
答案 8 :(得分:0)
没有必要使用 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);
}
}
}