Hibernate 有时JPA存储库不会';不返回最新数据?

Hibernate 有时JPA存储库不会';不返回最新数据?,hibernate,jpa,spring-data-jpa,Hibernate,Jpa,Spring Data Jpa,当从一个线程检索实体并在另一个线程中修改实体时,我看到了JPA的“奇怪”行为。 下面是我解释这个问题的示例代码。 典型情况: 创建一个学生s1 获取/打印循环中s1的学生详细信息 更新s1详细信息 检查是否在循环中打印更新的学生详细信息 它仍然打印旧数据 据我所知,JPA具有与每个线程关联的持久性上下文(PC),因此在其中存储实体。线程看不到彼此的PC。我认为如果相应的线程在实体上创建/更新操作,PC会得到更新。 我认为JPA的这种行为打破了数据库隔离原则。GET不应该直接返回更新的数

当从一个线程检索实体并在另一个线程中修改实体时,我看到了JPA的“奇怪”行为。 下面是我解释这个问题的示例代码。 典型情况:

  • 创建一个学生s1
  • 获取/打印循环中s1的学生详细信息
  • 更新s1详细信息
  • 检查是否在循环中打印更新的学生详细信息
    • 它仍然打印旧数据
据我所知,JPA具有与每个线程关联的持久性上下文(PC),因此在其中存储实体。线程看不到彼此的PC。我认为如果相应的线程在实体上创建/更新操作,PC会得到更新。 我认为JPA的这种行为打破了数据库隔离原则。GET不应该直接返回更新的数据吗?还是我在这里遗漏了什么

日志

实体

@Entity
public class Student {

    @Id
    private int id;

    private String name;

    private int score;
    ...
存储库

public interface StudentRepository extends JpaRepository<Student, Integer> {
}
public interface StudentRepository扩展了JpaRepository{
}
控制器

@RestController
@RequestMapping("/api/student")
public class StudentController {

    @Autowired
    private StudentRepository studentRepository;

    private final static Logger LOGGER = LoggerFactory.getLogger(StudentController.class);

    @GetMapping
    public void getStudent() {
        while (true) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Student student = studentRepository.findById(1).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
            LOGGER.info("Get student {}",student.toString());
        }
    }

    @PostMapping
    public ResponseEntity<Student> updateStudent(@RequestParam("score") int score) {
        Student student = studentRepository.findById(1).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
        student.setScore(score);
        Student updated = studentRepository.save(student);
        LOGGER.info("Updating student... {}", updated);
        return ResponseEntity.ok(updated);
    }

    @PutMapping
    public ResponseEntity<Student> createStudent(Student student) {
        Student saved = studentRepository.save(student);
        LOGGER.info("Created student... {}", saved);
        return ResponseEntity.created(URI.create("/api/student/" + saved.getId())).body(saved);
    }

}
@RestController
@请求映射(“/api/student”)
公共班级学生控制员{
@自动连线
私立学校;私立学校;
私有最终静态记录器Logger=LoggerFactory.getLogger(StudentController.class);
@GetMapping
公众假期学生(){
while(true){
试一试{
睡眠(5000);
}捕捉(中断异常e){
e、 printStackTrace();
}
Student Student=studentRepository.findById(1).OrelsThrow(()->新的ResponseStatusException(HttpStatus.NOT_FOUND));
LOGGER.info(“获取学生{}”,student.toString());
}
}
@邮戳
公共响应性更新学生(@RequestParam(“score”)int score){
Student Student=studentRepository.findById(1).OrelsThrow(()->新的ResponseStatusException(HttpStatus.NOT_FOUND));
学生成绩(分数);
学生更新=studentRepository.save(学生);
info(“更新学生…{}”,已更新);
返回响应正确(已更新);
}
@PutMapping
公共响应创建学生(学生){
保存的学生=studentRepository.save(学生);
info(“已创建学生…{}”,已保存);
返回ResponseEntity.created(URI.create(“/api/student/”+saved.getId()).body(saved);
}
}

将循环更改为此

@GetMapping
public void getStudent() {
    while (true) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        entityManager.clear();
        Student student = studentRepository.findById(1).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
        LOGGER.info("Get student {}",student.toString());
    }
}

基本上,findById的结果被附加到当前事务,查询只被调用一次(即使它在循环中)。要再次命中数据库,应分离实体。您可以通过调用entityManager.clear()来分离实体。

您可以在更新之前添加@Transactional并查看发生了什么。无更改。我认为@Transactional将处理是否提交/回滚工作单元,我认为JPA使代码变得复杂且无法维护。另外,如果不使用
flush()
,则
clear()
可能会导致随机错误。我在这个问题上的观点更多的是JPA行为的“为什么”。JPA是数据库和应用程序之间的中介。我认为它应该反映DB的现状。
@GetMapping
public void getStudent() {
    while (true) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        entityManager.clear();
        Student student = studentRepository.findById(1).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
        LOGGER.info("Get student {}",student.toString());
    }
}