I set isolation level to READ_UNCOMMITTED, use debug point to update value between two select query, I assert the first and second read value is not the same, but actually the second select value is equals the first one.
below is my source code
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void readUncommitted() {
String firstReadPassword = userRepository.findFirstByUsername("admin").map(User::getPassword).get();
log.info("first read user admin password is {}", firstReadPassword);
// debug point below, and do update password
String secondReadPassword = userRepository.findFirstByUsername("admin").map(User::getPassword).get();
log.info("second read user admin password is {}", secondReadPassword);
assertNotEquals(firstReadPassword, secondReadPassword);
}
}
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
Optional<User> findFirstByUsername(String username);
}
below is my test code
@SpringBootTest
public class ReadUncommittedTest {
@Configuration
@ComponentScan("com.pohvii.playground.transaction.readuncommitted.service")
@EntityScan("com.pohvii.playground.users.entity")
@EnableJpaRepositories(basePackages = {"com.pohvii.playground.transaction.readuncommitted.repository"})
@Import({
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
})
@EnableTransactionManagement
static class TestConfiguration {
}
@Autowired
private UserService userService;
@Test
void readUncommittedTest() throws Exception {
userService.readUncommitted();
}
}
below is my test log
2021-05-08 10:15:27.939 TRACE 45788 --- [ main] t.a.AnnotationTransactionAttributeSource : Adding transactional method 'com.pohvii.playground.transaction.readuncommitted.service.impl.UserServiceImpl.readUncommitted' with attribute: PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED
2021-05-08 10:15:31.497 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.jdbc.datasource.ConnectionHolder@7b5021d1] for key [HikariDataSource (NotebookHikariCP)] to thread [main]
2021-05-08 10:15:31.498 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] to thread [main]
2021-05-08 10:15:31.498 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Initializing transaction synchronization
2021-05-08 10:15:31.498 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : Getting transaction for [com.pohvii.playground.transaction.readuncommitted.service.impl.UserServiceImpl.readUncommitted]
2021-05-08 10:15:32.681 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] bound to thread [main]
2021-05-08 10:15:32.681 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@7b5021d1] for key [HikariDataSource (NotebookHikariCP)] bound to thread [main]
2021-05-08 10:15:32.681 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByUsername]
2021-05-08 10:15:32.686 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] bound to thread [main]
2021-05-08 10:15:32.769 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] bound to thread [main]
2021-05-08 10:15:32.777 DEBUG 45788 --- [ main] org.hibernate.SQL : select user0_.id as id1_0_, user0_.password as password2_0_, user0_.password_salt as password3_0_, user0_.role as role4_0_, user0_.username as username5_0_, user0_.version as version6_0_ from users user0_ where user0_.username=? limit ?
2021-05-08 10:15:32.914 TRACE 45788 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [admin]
2021-05-08 10:15:32.977 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByUsername]
2021-05-08 10:15:32.977 INFO 45788 --- [ main] c.p.p.t.r.service.impl.UserServiceImpl : first read user admin password is TohH
2021-05-08 10:15:33.038 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-05-08 10:15:33.045 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-05-08 10:16:11.336 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] bound to thread [main]
2021-05-08 10:16:11.337 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@7b5021d1] for key [HikariDataSource (NotebookHikariCP)] bound to thread [main]
2021-05-08 10:16:11.337 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByUsername]
2021-05-08 10:16:11.337 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] bound to thread [main]
2021-05-08 10:16:11.337 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] bound to thread [main]
2021-05-08 10:16:11.345 DEBUG 45788 --- [ main] org.hibernate.SQL : select user0_.id as id1_0_, user0_.password as password2_0_, user0_.password_salt as password3_0_, user0_.role as role4_0_, user0_.username as username5_0_, user0_.version as version6_0_ from users user0_ where user0_.username=? limit ?
2021-05-08 10:16:11.345 TRACE 45788 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [admin]
2021-05-08 10:16:11.567 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByUsername]
2021-05-08 10:16:11.589 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-05-08 10:16:11.597 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-05-08 10:16:33.829 INFO 45788 --- [ main] c.p.p.t.r.service.impl.UserServiceImpl : second read user admin password is TohH
2021-05-08 10:16:33.859 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.toString]: This method is not transactional.
2021-05-08 10:16:35.791 TRACE 45788 --- [ main] o.s.t.i.TransactionInterceptor : Completing transaction for [com.pohvii.playground.transaction.readuncommitted.service.impl.UserServiceImpl.readUncommitted] after exception: org.opentest4j.AssertionFailedError: expected: not equal but was: <TohH>
2021-05-08 10:16:35.791 TRACE 45788 --- [ main] o.s.t.i.RuleBasedTransactionAttribute : Applying rules to determine whether transaction should rollback on org.opentest4j.AssertionFailedError: expected: not equal but was: <TohH>
2021-05-08 10:16:35.791 TRACE 45788 --- [ main] o.s.t.i.RuleBasedTransactionAttribute : Winning rollback rule is: null
2021-05-08 10:16:35.791 TRACE 45788 --- [ main] o.s.t.i.RuleBasedTransactionAttribute : No relevant rollback rule found: applying default rules
2021-05-08 10:16:35.878 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Clearing transaction synchronization
2021-05-08 10:16:35.878 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.orm.jpa.EntityManagerHolder@21d9cd04] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@260e3837] from thread [main]
2021-05-08 10:16:35.878 TRACE 45788 --- [ main] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.jdbc.datasource.ConnectionHolder@7b5021d1] for key [HikariDataSource (NotebookHikariCP)] from thread [main]
Assign the results of both calls to findFirstByUsername().get()
to separate variables, and while debugging, compare their reference ids. You'll have your answer.
A proper test involves calling EntityManager.clear()
in between the two calls.