Foreign
Foreign

Reputation: 405

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

The issue here is that both threads are executing the first SELECT at the same time. Considering that saveUser is a @Transactional method, should't the second thread wait until the first thread commit/rollback?

Code:

@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 protected]"));
        t1.setName("Thread #1");
        t1.start();

        Thread t2 = new Thread(() -> us.saveUser("[email protected]"));
        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;
    }
}

Output:

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

Upvotes: 1

Views: 2237

Answers (3)

Lesiak
Lesiak

Reputation: 26064

@Transactional has isolation parameter that specifies isolation level of your transactions.

The transaction isolation level. Defaults to Isolation.DEFAULT.

Default means the default isolation selected by your db. The behaviour you want is described by Serializable isolation level, which, most likely is not the default (Just to name a few: Postgres, MsSql Server, Oracle default to Read Commited)

Upvotes: 2

venkat
venkat

Reputation: 117

if you want to control multiple threads in transactional environment, you have to use isolation levels.

one example:

@Transactional(isolation = Isolation.SERIALIZABLE)

Upvotes: 2

Thomas Lazer
Thomas Lazer

Reputation: 178

You will have to set the Transaction context for each individual thread for that to happen.

Upvotes: 0

Related Questions