线程之间未共享休眠会话 [英] Hibernate session not shared between threads

查看:87
本文介绍了线程之间未共享休眠会话的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个实现用户推荐系统的springboot应用程序.一个用例是,当一个用户使用来自另一个用户的有效推荐代码进行注册时,引荐用户获得一个奖励积分,每获得5个积分,他们将获得10美元的信用额度.据此,我在我的应用程序中实现了一个遵循这些业务规则的用例,并且为了测试高并发性下的正确行为,我创建了一个集成测试,使用@DataJpaTest和spring数据存储库以及H2 DB作为存储系统.在测试中,我创建了第一个用户,然后使用第一个用户引用代码创建了一定数量的用户,其中每个用户都是使用线程池执行程序在不同的线程上创建的,以产生这些线程.我的问题是,即使我使用JpaRepository.saveAndFlush()方法保存用户,通过线程池生成的线程创建的用户也看不到主线程中创建的第一个用户.

I have a springboot application that implements a user referral system. One use case is that when a user signs up using a valid referral code from another user, the referrer user gets one reward point, and for every five points they get 10$ in credit. According to this, I have implemented a use case in my application that honors these business rules, and to test proper behavior under high concurrency, I've created an integration test using @DataJpaTest and spring data repositories and H2 DB as storage system. In my test, I create a first user, and then I create a certain amount of users using the first user referral code, every one of those users is created on a different thread using a thread pool executor to spawn those threads. My problem is that the users created through thread pool spawned threads don't see the first user created in the main thread, even though I'm using JpaRepository.saveAndFlush() method to save them.

有人可以给我解释一下这里发生的事情吗?是因为Hibernate的会话不是线程安全的吗?

Could someone give me an explanation about what's happening here? Is it because Hibernate's session is not thread-safe?

您可以在下面看到我的代码,第一个测试已简化为仅检查存储库中的用户数量.

You can see my code below, the first test has been simplified to just check the amount of user's in the repository.

@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
public class JpaTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    @Qualifier("JpaUserRepository")
    private JpaUserRepository userRepository;

    @Autowired
    @Qualifier("JpaReferralRepository")
    private ReferralRepository referralRepository;

    private RegisterReferredUser registerReferredUser;
    private CreateUser createUser;
    private GetUser getUser;

    @BeforeEach
    void setUp() {
        registerReferredUser = new RegisterReferredUser(referralRepository, userRepository);
        createUser = new CreateUser(userRepository, referralRepository, registerReferredUser);
        getUser = new GetUser(userRepository);
    }

    @Test
    void createUser_shouldWorksProperlyUnderConcurrentExecution() throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        EmailAddress referrerUserEmail = EmailAddress.of("john.doe@acme.inc");
        User referrerUser = createUser.execute(new CreateUserCommand(referrerUserEmail.getValue(), null));
        String referralCode = referrerUser.getReferralCode().getValue();
        int maxIterations = 10;

        for (int i = 0; i < maxIterations; i++) {
            int emailSeed = i;
            executor.submit(() -> {
                    createUser.execute(new CreateUserCommand(anEmailAddress(emailSeed), referralCode));
            });
        }

        executor.shutdown();
        if (!executor.awaitTermination(20, TimeUnit.SECONDS)) {
            fail("Executor didn't finish in time");
        }

        assertThat(entityManager.getEntityManager().createQuery("from JpaUser").getResultList().size()).isEqualTo(maxIterations + 1);
        // This fails: just 1 user in the repository, however, if I register users without referral (without checking the existence of the first user), users are created and this pass
    }

    @Test
    void just_a_POC() throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        userRepository.save(UserMother.aUserWithEmail("john.doe@acme.inc"));
        int maxIterations = 10;

        for (int i = 0; i < maxIterations; i++) {
            int emailSeed = i;
            executor.submit(() -> userRepository.save(UserMother.aUserWithEmail(anEmailAddress(emailSeed))));
        }

        executor.shutdown();
        if (!executor.awaitTermination(20, TimeUnit.SECONDS)) {
            fail("Executor didn't finish in time");
        }

        assertThat(entityManager.getEntityManager().createQuery("from JpaUser").getResultList().size()).isEqualTo(maxIterations + 1);
        // This pass
    }
}

CreateUser中,我有以下代码:

private void assertReferralCodeIsValid(ReferralCode referralCode, EmailAddress email) {
    if (!userRepository.findByReferralCode(referralCode).isPresent()) {
        throw new NonExistentReferralCode(referralCode);
    }
    if (referralRepository.findByEmailAndCode(email, referralCode).isPresent()) {
        throw new ReferralCodeAlreadyUsed(email, referralCode);
    }
}

这是JpaUserRepository.save()方法:

@Repository("JpaUserRepository")
public class JpaUserRepository implements UserRepository {
    
    private JpaUserCrudRepository crudRepository;

    public JpaUserRepository(JpaUserCrudRepository crudRepository) {
        this.crudRepository = crudRepository;
    }
       
    @Override
    public void save(User user) {
        crudRepository.saveAndFlush(JpaUser.fromDomain(user));
    }

}

推荐答案

查看为您的事务配置的隔离级别.数据库引擎通常尝试尽可能快地提供数据而不会阻塞(如果可能).因此,如果您的所有线程同时读取一个表,则它们可能会收到未提交"消息.记录的版本.

Look at the isolation level configured for your transactions. Database engines usually try to serve data as fast as possible without blocking (when possible). So if all your threads read a table at the same time, they may get an "uncommited" version of the records.

如果需要同步,则可以更改隔离级别,或在处理表之前将其锁定.

If you need synchronization, you can change the isolation level, or lock the table before working on it.

有关此主题的更多信息:

More on this topic:

  • Spring @Transactional - isolation, propagation
  • https://www.baeldung.com/java-jpa-transaction-locks

这篇关于线程之间未共享休眠会话的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆