【问题标题】:Spring @Transactional: Should't the second thread wait until the first thread commit/rollback?Spring @Transactional:第二个线程不应该等到第一个线程提交/回滚吗?
【发布时间】:2020-09-12 20:18:38
【问题描述】:

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

代码:

@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;
[...]

表格:

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

【问题讨论】:

  • 没有,为什么要呢?它们只是 2 个单独的交易。为什么数据库应该只允许单个事务?仅当您使用悲观锁定(如果您的数据库支持它)和/或序列化事务时,才会出现这种情况。然而,两者都是性能杀手,因为它只允许对数据库进行串行访问。

标签: spring spring-boot spring-data-jpa spring-data spring-transactions


【解决方案1】:

您必须为每个单独的线程设置事务上下文才能发生这种情况。

【讨论】:

    【解决方案2】:

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

    一个例子:

    @Transactional(isolation = Isolation.SERIALIZABLE)
    

    【讨论】:

      【解决方案3】:

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

      事务隔离级别。 默认为 Isolation.DEFAULT。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多