Spring @Transactional: разве второй поток не должен ждать фиксации / отката первого потока? - PullRequest
0 голосов
/ 26 мая 2020

Проблема в том, что оба потока выполняют первую 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);

Ответы [ 3 ]

1 голос
/ 26 мая 2020

, если вы хотите управлять несколькими потоками в транзакционной среде, вы должны использовать уровни изоляции.

один пример:

@Transactional(isolation = Isolation.SERIALIZABLE)
1 голос
/ 26 мая 2020

@ Transactional имеет параметр изоляция , который определяет уровень изоляции ваших транзакций.

Уровень изоляции транзакции. По умолчанию Isolation.DEFAULT.

Default означает изоляцию по умолчанию, выбранную вашей базой данных. Желаемое поведение описывается уровнем изоляции Serializable, который, скорее всего, не является значением по умолчанию (просто чтобы назвать несколько: Postgres, Ms Sql Server, Oracle по умолчанию Read Commited)

0 голосов
/ 26 мая 2020

Для этого вам нужно будет установить контекст транзакции для каждого отдельного потока.

...