出于教学目的,我想提供一个脏读示例。使用 READ_UNCOMMITED 隔离级别。但是我无法让这个例子工作。我尝试了很多不同的东西,但它仍然不起作用。所以我希望你能帮助我。
设想:
- 启动事务“main”</li>
- 在新交易中插入人(“主要”交易被暂停)
- 更新主交易中的人名
- 刷新更新
- 在新事务中读取人员(隔离级别为 READ_UNCOMMITTED)
- 应该读取更新人的数据 --> 这不起作用!
- 通过抛出 RuntimeException 恢复主事务
- 断言该人的原始姓名在数据库中->这有效
有关更多信息,请参见下面的代码。
我还针对 Postgres 数据库进行了测试,但这并没有什么不同。还尝试使用 JDBC 模板而不是 ORM 映射器来做所有事情,但这也没有什么区别。
提前致谢。
最好的问候, 马里努斯
IsolationLevelTest (src/test/java/test)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringBootTestApplication.class})
public class IsoliationLevelTest {
@Autowired
private TestService testService;
@Autowired
private UtilService serviceUtil;
@After
public void tearDown() {
serviceUtil.deleteTestPersons();
}
@Test
public void testIsolationLevel() {
try {
testService.testIsolationLevel("Piet", "PietUpdated");
} catch (TestService.TestException e) {
List<PersonJPAEntity> persons = serviceUtil.retrieveTestPersons();
assertEquals(1, persons.size());
assertEquals("Piet", persons.get(0).getName());
assertEquals("PietUpdated", e.getPersonReadInNewTransaction().getName());
}
}
}
SpringBootTestApplication (src/main/java/test)
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class SpringBootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTestApplication.class, args);
}
}
测试服务
@Service
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public class TestService {
Logger logger = LoggerFactory.getLogger(TestService.class);
@Autowired
private PersonRepository personRepository;
@Transactional(propagation = REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
public void testIsolationLevel(String initialName, String newName) {
//Requires_new propagation so transaction is committed after person is save
TestService self = (TestService) AopContext.currentProxy();
self.insertPerson(new PersonJPAEntity(1, initialName));
//Required propagation so this update runs in current transaction (which is not committed yet)
self.updatePerson(newName);
PersonJPAEntity personReadInNewTransaction = self.findPersonInNewTransaction();
logger.info("Throw exception and thereby rollback transaction");
throw new TestException("Rollback transaction", personReadInNewTransaction);
}
@Transactional(propagation = REQUIRES_NEW)
public void insertPerson(PersonJPAEntity person) {
personRepository.save(person);
logger.info("Person inserted {}", person);
}
@Transactional(propagation = REQUIRED)
public void updatePerson(String newName) {
Optional<PersonJPAEntity> personToUpdate = personRepository.findById(1);
personToUpdate.get().setName(newName);
logger.info("Person updated {}", personToUpdate);
personRepository.flush();
logger.info("Repository flushed");
}
@Transactional(propagation = REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
public PersonJPAEntity findPersonInNewTransaction() {
Optional<PersonJPAEntity> person = personRepository.findById(1);
logger.info("Person found in new transaction {}", person);
return person.get();
}
public class TestException extends RuntimeException {
private final PersonJPAEntity personReadInNewTransaction;
public TestException(String message, PersonJPAEntity personReadInNewTransaction) {
super(message);
this.personReadInNewTransaction = personReadInNewTransaction;
}
public PersonJPAEntity getPersonReadInNewTransaction() {
return personReadInNewTransaction;
}
}
}
个人资料库
public interface PersonRepository extends JpaRepository<PersonJPAEntity, Integer> {
List<PersonJPAEntity> findByOrderByIdAsc();
}
实用服务
@Service
public class UtilService {
@Autowired
PersonRepository personRepository;
@Transactional(propagation = REQUIRES_NEW)
public List<PersonJPAEntity> retrieveTestPersons() {
return personRepository.findByOrderByIdAsc();
}
@Transactional(propagation = REQUIRES_NEW)
public void deleteTestPersons() {
personRepository.deleteAll();
}
}
PersonJPAEntity (src/main/java/test)
@Entity(name = "person")
public class PersonJPAEntity {
@Id
private int id;
private String name;
private PersonJPAEntity() {
}
PersonJPAEntity(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PersonJPAEntity)) return false;
PersonJPAEntity person = (PersonJPAEntity) o;
return id == person.id &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
application.properties (src/main/resources)
# spring.jpa.show-sql=true
logging.level.org.springframework.transaction=TRACE