为什么发生RuntimeException时事务不回滚?
Why transactional does not rollback when RuntimeException occur?
我想在服务层中测试一个非事务性方法,而内部方法是单独事务性的。我想知道如果某些方法抛出异常会发生什么。它是否正确回滚?我也试过 rollbackFor
但没有用。有什么建议吗?
- Spring v-2.5.1
app.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create-drop
server.error.include-message=always
server.error.include-binding-errors=always
控制器
@PostMapping("/test")
public void test(){
service.test();
}
服务
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public void test() {
User user1 = new User();
String encodedPassword = passwordEncoder.encode("123");
user1.setUsername("username");
user1.setPassword(encodedPassword);
user1.setFirst_name("name1");
user1.setLast_name("family1");
save(user1);
User update = update(user1.getUsername());
throw new RuntimeException();// I expect Spring rollback all data for save and update methods
//but it seems data has been committed before an exception occur.
}
@Transactional
public void save(User user) {
if (userExists(user.getUsername()))
throw new ApiRequestException("Username has already taken!");
else {
User user1 = new User();
String encodedPassword = passwordEncoder.encode(user.getPassword());
user1.setUsername(user.getUsername());
user1.setPassword(encodedPassword);
user1.setFirst_name(user.getFirst_name());
user1.setLast_name(user.getLast_name());
user1.setCreate_date(user.getCreate_date());
user1.setModified_date(user.getModified_date());
user1.setCompany(user.getCompany());
user1.setAddresses(user.getAddresses());
userRepository.save(user1);
// throw new RuntimeException(); Similarly I expect rollback here.
}
}
@Transactional
public User update(String username) {
User userFromDb = userRepository.findByUsername(username);
userFromDb.setFirst_name("new username");
userFromDb.setLast_name("new lastname");
return userRepository.save(userFromDb);
}
}
首先,我不确定您是否理解交易代表什么。如果有一个工作单元包含多个要执行的功能,并且您希望它们完全失败或完全通过 - 那就是您使用事务的时候。
所以在 test()
方法的情况下 - 为什么底层机制(在本例中是 Hibernate)关心回滚 save()
和 update()
中发生的事情,当 test()
本身不是交易?
很抱歉问的比回答的多,但据我所知 - 您需要用注释而不是单个方法来注释 test()
。或者你可以把它们都注释掉。
由于 spring 代理的工作方式 默认情况下 ,您不能从实例中调用“代理”方法。
考虑一下,当您在方法上放置 @Transactional
注释时,Spring 会创建该 class 的代理,而代理是处理事务 begin/commit/rollback 的地方。代理调用实际的 class 实例。 Spring 对你隐藏了这个。
但是鉴于此,如果您在 class 实例 (test()
) 上有一个方法,它会调用自身的另一个方法 (this.save()
),则该调用不会进行通过代理,所以没有“@Transactional”代理。 RuntimeException发生时无事回滚
有一些方法可以改变 Spring 代理的方式,这将使它起作用,但多年来它已经发生了变化。有多种选择。一种方法是创建单独的 classes。也许是 UserServiceHelper。 UserServiceHelper 包含由 UserService 调用的@Transactional 方法。当需要不同的@Transactional 隔离和传播时,也会出现同样的问题。
相关答案和信息:
- Spring @Transaction method call by the method within the same class, does not work?
- Does Spring @Transactional attribute work on a private method?
- Spring Transaction Doesn't Rollback
您的示例代码对于您要执行的操作不是很清楚。通常,@Transactional
将放在服务 class 上并应用于所有 public 方法(如 test()
)。
我想在服务层中测试一个非事务性方法,而内部方法是单独事务性的。我想知道如果某些方法抛出异常会发生什么。它是否正确回滚?我也试过 rollbackFor
但没有用。有什么建议吗?
- Spring v-2.5.1
app.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create-drop
server.error.include-message=always
server.error.include-binding-errors=always
控制器
@PostMapping("/test")
public void test(){
service.test();
}
服务
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public void test() {
User user1 = new User();
String encodedPassword = passwordEncoder.encode("123");
user1.setUsername("username");
user1.setPassword(encodedPassword);
user1.setFirst_name("name1");
user1.setLast_name("family1");
save(user1);
User update = update(user1.getUsername());
throw new RuntimeException();// I expect Spring rollback all data for save and update methods
//but it seems data has been committed before an exception occur.
}
@Transactional
public void save(User user) {
if (userExists(user.getUsername()))
throw new ApiRequestException("Username has already taken!");
else {
User user1 = new User();
String encodedPassword = passwordEncoder.encode(user.getPassword());
user1.setUsername(user.getUsername());
user1.setPassword(encodedPassword);
user1.setFirst_name(user.getFirst_name());
user1.setLast_name(user.getLast_name());
user1.setCreate_date(user.getCreate_date());
user1.setModified_date(user.getModified_date());
user1.setCompany(user.getCompany());
user1.setAddresses(user.getAddresses());
userRepository.save(user1);
// throw new RuntimeException(); Similarly I expect rollback here.
}
}
@Transactional
public User update(String username) {
User userFromDb = userRepository.findByUsername(username);
userFromDb.setFirst_name("new username");
userFromDb.setLast_name("new lastname");
return userRepository.save(userFromDb);
}
}
首先,我不确定您是否理解交易代表什么。如果有一个工作单元包含多个要执行的功能,并且您希望它们完全失败或完全通过 - 那就是您使用事务的时候。
所以在 test()
方法的情况下 - 为什么底层机制(在本例中是 Hibernate)关心回滚 save()
和 update()
中发生的事情,当 test()
本身不是交易?
很抱歉问的比回答的多,但据我所知 - 您需要用注释而不是单个方法来注释 test()
。或者你可以把它们都注释掉。
由于 spring 代理的工作方式 默认情况下 ,您不能从实例中调用“代理”方法。
考虑一下,当您在方法上放置 @Transactional
注释时,Spring 会创建该 class 的代理,而代理是处理事务 begin/commit/rollback 的地方。代理调用实际的 class 实例。 Spring 对你隐藏了这个。
但是鉴于此,如果您在 class 实例 (test()
) 上有一个方法,它会调用自身的另一个方法 (this.save()
),则该调用不会进行通过代理,所以没有“@Transactional”代理。 RuntimeException发生时无事回滚
有一些方法可以改变 Spring 代理的方式,这将使它起作用,但多年来它已经发生了变化。有多种选择。一种方法是创建单独的 classes。也许是 UserServiceHelper。 UserServiceHelper 包含由 UserService 调用的@Transactional 方法。当需要不同的@Transactional 隔离和传播时,也会出现同样的问题。
相关答案和信息:
- Spring @Transaction method call by the method within the same class, does not work?
- Does Spring @Transactional attribute work on a private method?
- Spring Transaction Doesn't Rollback
您的示例代码对于您要执行的操作不是很清楚。通常,@Transactional
将放在服务 class 上并应用于所有 public 方法(如 test()
)。