Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
220 views
in Technique[技术] by (71.8m points)

java - Spring Boot: save to repository doesnt work with @Transactional(propagation = Propagation.REQUIRES_NEW)

my application is divided into modules, I wanted to create new "confirm registration token" module which is independent from "user" module perspective. As you can guess this module is responsible for enabling user account via email verification token. So, I've created entity with one-to-one relation to user:

@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.

I'm listening to OnUserRegisterEvent that is published in UserService:

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

Then, createToken is fired in the listener that is annotated with @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 method is annotated with @Transactional(propagation = Propagation.REQUIRES_NEW), because I want to fire this method in new, independent transaction:

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

Bu unfortunally, saving token to databse doesn't work, query is not even sent to db:

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 (?, ?, ?, ?)

I've tested saving this token in other methods using @Transactional with default values and it works, here it doesn't and I'm confused because there are no any exception here. Am I doing something wrong?

question from:https://stackoverflow.com/questions/65926805/spring-boot-save-to-repository-doesnt-work-with-transactionalpropagation-pr

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If you're using simple @Transactional and go into the second method the transaction is still the same so it works.

I believe that it is because you're passing entity already saved in the previous transaction. User -> createToken

@Transactional methods are automatically reflected in the database at the end of transaction, but it only applies to the entities saved, merged or retrieved within this specific transaction.

Generally, passing entities between different transaction contexts is not a good idea. You should pass the only id and fetch again the whole entity if you need it in a new transaction.

Here is a good article about your issue: https://codete.com/blog/spring-transaction-propagation-modes/

I believe that It'll help you.

Example of code:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...