Spring 引导:保存到存储库不适用于 @Transactional(propagation = Propagation.REQUIRES_NEW)
Spring Boot: save to repository doesnt work with @Transactional(propagation = Propagation.REQUIRES_NEW)
我的应用程序分为多个模块,我想创建独立于“用户”模块视角的新“确认注册令牌”模块。您可以猜到该模块负责通过电子邮件验证令牌启用用户帐户。因此,我创建了与用户具有一对一关系的实体:
@Entity
public class ConfirmRegistrationToken {
@Id
private Long id;
@MapsId
@OneToOne(optional = false, fetch = FetchType.LAZY)
private User user;
private String token;
private LocalDateTime expiresAt;
// setters, getters, etc.
我正在收听在 UserService 中发布的 OnUserRegisterEvent:
@Transactional
public UserModel register(UserRegisterModel model) {
validatorsExecutor.validate(
new UserRegisterValidator(model, userRepository)
);
User user = userRegisterMapper.toObject(model);
user = userRepository.save(user);
eventPublisher.publishEvent(new OnUserRegisterEvent(user));
return userMapper.toModel(user);
}
然后,createToken 在带有 @TransactionalEventListener 注释的侦听器中触发:
@Component
public class OnUserRegisterListener {
private final ConfirmRegistrationTokenGenerator tokenGenerator;
@Autowired
public OnUserRegisterListener(ConfirmRegistrationTokenGenerator tokenGenerator) {
this.tokenGenerator = tokenGenerator;
}
@TransactionalEventListener
public void onApplicationEvent(OnUserRegisterEvent event) {
tokenGenerator.createToken(event.getUser());
}
}
createToken 方法用@Transactional(propagation = Propagation.REQUIRES_NEW) 注释,因为我想在新的独立事务中触发此方法:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createToken(User user) {
ConfirmRegistrationToken token = new ConfirmRegistrationToken();
token.setUser(user);
String generatedToken = UUID.randomUUID().toString();
token.setToken(generatedToken);
Integer expirationHours = environment.getProperty("user.confirm-registration.expiration-hours", Integer.class, 24);
token.setExpiresAt(LocalDateTime.now().plus(Duration.ofHours(expirationHours)));
confirmRegistrationTokenRepository.save(token);
//TODO implement sending message here
System.out.println("To confirm your registration, just click here: http://localhost:8080/user/" + user.getId() + "/confirmRegistration/" + generatedToken);
}
不幸的是,将令牌保存到数据库不起作用,查询甚至没有发送到数据库:
Hibernate: select user0_.id as id1_3_, user0_.email as email2_3_, user0_.enabled as enabled3_3_, user0_.encoded_password as encoded_4_3_, user0_.name as name5_3_ from users user0_ where user0_.email=?
Hibernate: select user0_.id as id1_3_, user0_.email as email2_3_, user0_.enabled as enabled3_3_, user0_.encoded_password as encoded_4_3_, user0_.name as name5_3_ from users user0_ where user0_.name=?
Hibernate: insert into users (email, enabled, encoded_password, name) values (?, ?, ?, ?)
我测试过在其他方法中使用带有默认值的@Transactional 来保存这个标记,它可以工作,这里不行,我很困惑,因为这里没有任何例外。我做错了什么吗?
如果您使用的是简单 @Transactional
并进入第二种方法,交易仍然是相同的,所以它有效。
我认为这是因为您传递的实体已保存在上一次交易中。 User
-> createToken
@Transactional方法会在事务结束时自动反映到数据库中,但只适用于保存、合并的实体或在此特定事务中检索。
通常,在不同事务上下文之间传递实体不是一个好主意。如果您在新事务中需要它,您应该传递唯一的 ID 并再次获取整个实体。
这是一篇关于您的问题的好文章:https://codete.com/blog/spring-transaction-propagation-modes/
相信对你有帮助。
代码示例:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createToken(long userId) {
User user = userRepository.findById(userId).get();
ConfirmRegistrationToken token = new ConfirmRegistrationToken();
token.setUser(user);
String generatedToken = UUID.randomUUID().toString();
token.setToken(generatedToken);
Integer expirationHours = environment.getProperty("user.confirm-registration.expiration-hours", Integer.class, 24);
token.setExpiresAt(LocalDateTime.now().plus(Duration.ofHours(expirationHours)));
confirmRegistrationTokenRepository.save(token);
//TODO implement sending message here
System.out.println("To confirm your registration, just click here: http://localhost:8080/user/" + user.getId() + "/confirmRegistration/" + generatedToken);
}
我的应用程序分为多个模块,我想创建独立于“用户”模块视角的新“确认注册令牌”模块。您可以猜到该模块负责通过电子邮件验证令牌启用用户帐户。因此,我创建了与用户具有一对一关系的实体:
@Entity
public class ConfirmRegistrationToken {
@Id
private Long id;
@MapsId
@OneToOne(optional = false, fetch = FetchType.LAZY)
private User user;
private String token;
private LocalDateTime expiresAt;
// setters, getters, etc.
我正在收听在 UserService 中发布的 OnUserRegisterEvent:
@Transactional
public UserModel register(UserRegisterModel model) {
validatorsExecutor.validate(
new UserRegisterValidator(model, userRepository)
);
User user = userRegisterMapper.toObject(model);
user = userRepository.save(user);
eventPublisher.publishEvent(new OnUserRegisterEvent(user));
return userMapper.toModel(user);
}
然后,createToken 在带有 @TransactionalEventListener 注释的侦听器中触发:
@Component
public class OnUserRegisterListener {
private final ConfirmRegistrationTokenGenerator tokenGenerator;
@Autowired
public OnUserRegisterListener(ConfirmRegistrationTokenGenerator tokenGenerator) {
this.tokenGenerator = tokenGenerator;
}
@TransactionalEventListener
public void onApplicationEvent(OnUserRegisterEvent event) {
tokenGenerator.createToken(event.getUser());
}
}
createToken 方法用@Transactional(propagation = Propagation.REQUIRES_NEW) 注释,因为我想在新的独立事务中触发此方法:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createToken(User user) {
ConfirmRegistrationToken token = new ConfirmRegistrationToken();
token.setUser(user);
String generatedToken = UUID.randomUUID().toString();
token.setToken(generatedToken);
Integer expirationHours = environment.getProperty("user.confirm-registration.expiration-hours", Integer.class, 24);
token.setExpiresAt(LocalDateTime.now().plus(Duration.ofHours(expirationHours)));
confirmRegistrationTokenRepository.save(token);
//TODO implement sending message here
System.out.println("To confirm your registration, just click here: http://localhost:8080/user/" + user.getId() + "/confirmRegistration/" + generatedToken);
}
不幸的是,将令牌保存到数据库不起作用,查询甚至没有发送到数据库:
Hibernate: select user0_.id as id1_3_, user0_.email as email2_3_, user0_.enabled as enabled3_3_, user0_.encoded_password as encoded_4_3_, user0_.name as name5_3_ from users user0_ where user0_.email=?
Hibernate: select user0_.id as id1_3_, user0_.email as email2_3_, user0_.enabled as enabled3_3_, user0_.encoded_password as encoded_4_3_, user0_.name as name5_3_ from users user0_ where user0_.name=?
Hibernate: insert into users (email, enabled, encoded_password, name) values (?, ?, ?, ?)
我测试过在其他方法中使用带有默认值的@Transactional 来保存这个标记,它可以工作,这里不行,我很困惑,因为这里没有任何例外。我做错了什么吗?
如果您使用的是简单 @Transactional
并进入第二种方法,交易仍然是相同的,所以它有效。
我认为这是因为您传递的实体已保存在上一次交易中。 User
-> createToken
@Transactional方法会在事务结束时自动反映到数据库中,但只适用于保存、合并的实体或在此特定事务中检索。
通常,在不同事务上下文之间传递实体不是一个好主意。如果您在新事务中需要它,您应该传递唯一的 ID 并再次获取整个实体。
这是一篇关于您的问题的好文章:https://codete.com/blog/spring-transaction-propagation-modes/
相信对你有帮助。
代码示例:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createToken(long userId) {
User user = userRepository.findById(userId).get();
ConfirmRegistrationToken token = new ConfirmRegistrationToken();
token.setUser(user);
String generatedToken = UUID.randomUUID().toString();
token.setToken(generatedToken);
Integer expirationHours = environment.getProperty("user.confirm-registration.expiration-hours", Integer.class, 24);
token.setExpiresAt(LocalDateTime.now().plus(Duration.ofHours(expirationHours)));
confirmRegistrationTokenRepository.save(token);
//TODO implement sending message here
System.out.println("To confirm your registration, just click here: http://localhost:8080/user/" + user.getId() + "/confirmRegistration/" + generatedToken);
}