Spring @Transactional:第二个线程不应该等到第一个线程commit/rollback吗?

Spring @Transactional: Should't the second thread wait until the first thread commit/rollback?

这里的问题是两个线程同时执行第一个 SELECT。考虑到 saveUser 是一个 @Transactional 方法,第二个线程不应该等到第一个线程 commit/rollback 吗?

代码:

@SpringBootApplication
public class TestApp
{
    public static void main(String[] args)
    {
        ConfigurableApplicationContext app = SpringApplication.run(TestApp.class, args);
        UserService us = (UserService) app.getBean("userService");

        Thread t1 = new Thread(() -> us.saveUser("email@email.com"));
        t1.setName("Thread #1");
        t1.start();

        Thread t2 = new Thread(() -> us.saveUser("email@email.com"));
        t2.setName("Thread #2");
        t2.start();
    }
}

@Repository
public interface UserRepository extends CrudRepository<UserService.User, Long>
{
    public UserService.User getByEmail(String email);
}

@AllArgsConstructor
@Service
public class UserService
{
    private final UserRepository userRepository;

    @Transactional
    public boolean saveUser(String email)
    {
        if (userRepository.getByEmail(email) != null)
        {
            System.out.println("User already exists");
            return false;
        }

        System.out.println(Thread.currentThread().getName() + ": User doesn't exists, sleeping..");

        try
        {
            Thread.sleep(5000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        User user = new User();
        user.email = email;

        System.out.println(Thread.currentThread().getName() + ": Saving user..");
        user = userRepository.save(user);

        return user.id > 0;
    }

    @Table("user")
    public static class User
    {
        @Id
        public long id;
        public String email;
    }
}

输出:

Thread #1: User doesn't exists, sleeping..
Thread #2: User doesn't exists, sleeping..
Thread #1: Saving user..
Thread #2: Saving user..
Exception in thread "Thread #2" org.springframework.data.relational.core.conversion.DbActionExecutionException: Failed to execute DbAction.InsertRoot(entity=testapp.UserService$User@3ce548a)
[...]
Caused by: org.springframework.dao.DuplicateKeyException: PreparedStatementCallback;
[...]

Table:

create table user (`id` int primary key auto_increment, `email` varchar(50) unique);

您必须为每个单独的线程设置事务上下文才能实现。

如果你想在事务环境中控制多个线程,你必须使用隔离级别。

一个例子:

@Transactional(isolation = Isolation.SERIALIZABLE)

@Transactional 有 isolation 指定事务隔离级别的参数。

The transaction isolation level. Defaults to Isolation.DEFAULT.

默认表示您的数据库选择的默认隔离。 您想要的行为由 Serializable 隔离级别描述,这很可能不是默认的(仅举几例:Postgres、MsSql Server、Oracle 默认为 Read Commited)