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