Hibernate中session.setFlushMode(FlushModeType.COMMIT)未按预期工作问题
给Hibernate的Session设置FlushModeType.COMMIT后,是不是应该只有显式提交事务时才刷新持久化上下文?我已经给Session和两个Query都设置了FlushModeType.COMMIT,执行session.persist(new Student("newStudent","newAddress"))后,预期会先执行两个SELECT查询,再在提交事务时执行INSERT,但实际却是先执行了INSERT语句,再执行SELECT查询。想知道这个现象的原因,也欢迎指出我对刷新机制的理解错误。
实体类
Employee类
import javax.persistence.*; @Entity @Table(name="employee") public class Employee { public Employee(){ } public Employee(String EmployeeName, String EmployeeAddress){ this.EmployeeName =EmployeeName; this.EmployeeAddress =EmployeeAddress; } @Column(name="employee_name") String EmployeeName; @Column(name="employee_id") @Id @GeneratedValue(strategy = GenerationType.IDENTITY) int EmployeeId; @Column(name="employee_address") String EmployeeAddress; // 所有getter、setter及toString方法 }
Student类
import javax.persistence.*; @Entity @Table(name="student") public class Student { public Student(){ } public Student(String studentName, String studentAddress){ this.studentName=studentName; this.studentAddress=studentAddress; } @Column(name="student_name") String studentName; @Column(name="student_id") @Id @GeneratedValue(strategy = GenerationType.IDENTITY) int studentId; @Column(name="student_address") String studentAddress; // 所有getter、setter及toString方法 }
Hibernate配置类
import Entities.Employee; import Entities.Student; import org.hibernate.SessionFactory; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Environment; import org.hibernate.service.ServiceRegistry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; @Configuration public class Configs { @Bean(name = "factory") public SessionFactory getSessionFactory(){ org.hibernate.cfg.Configuration configuration=new org.hibernate.cfg.Configuration(); StandardServiceRegistryBuilder builder=new StandardServiceRegistryBuilder(); Properties properties=new Properties(); properties.put(Environment.URL,"jdbc:mysql://localhost:3306/practiceHibernate"); properties.put(Environment.DRIVER,"com.mysql.cj.jdbc.Driver"); properties.put(Environment.PASS,"root"); properties.put(Environment.USER,"root"); properties.put(Environment.CURRENT_SESSION_CONTEXT_CLASS,"thread"); properties.put(Environment.SHOW_SQL,true); properties.put(Environment.DIALECT,"org.hibernate.dialect.MySQL8Dialect"); configuration.addAnnotatedClass(Student.class).addAnnotatedClass(Employee.class); configuration.addProperties(properties); ServiceRegistry service=builder.applySettings(properties).build(); SessionFactory sessionFactory= configuration.buildSessionFactory(service); return sessionFactory; } }
主代码
import Entities.Employee; import Entities.Student; import org.hibernate.FlushMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.query.Query; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import javax.persistence.FlushModeType; import java.util.List; public class Main { public static void main(String[] args) { ApplicationContext context=new AnnotationConfigApplicationContext(Configs.class); SessionFactory factory=context.getBean("factory",SessionFactory.class); Session session=factory.openSession(); session.setFlushMode(FlushModeType.COMMIT); session.beginTransaction(); session.persist(new Student("newStudent","newAddress")); Query<Employee> q=session.createQuery("select o from Employee o where o.EmployeeId>=1", Employee.class); q.setFlushMode(FlushModeType.COMMIT); List<Employee> list= q.getResultList(); for(Employee o:list){ System.out.println(o); } System.out.println("------------------------------------------------------------------------------"); Query<Student> q1=session.createQuery("select s from Student s where s.studentId>=15",Student.class); q1.setFlushMode(FlushModeType.COMMIT); List<Student> list1= q1.getResultList(); for(Student o:list1){ System.out.println(o); } session.getTransaction().commit(); factory.close(); } }
实际输出
Hibernate: insert into student (student_address, student_name) values (?, ?) Hibernate: select employee0_.employee_id as employee1_0_, employee0_.employee_address as employee2_0_, employee0_.employee_name as employee3_0_ from employee employee0_ where employee0_.employee_id>=1 Employee{EmployeeName='employee1', EmployeeId=1, EmployeeAddress='employee1Address'} Employee{EmployeeName='employee2', EmployeeId=2, EmployeeAddress='employee2Address'} ------------------------------------------------------------------------------ Hibernate: select student0_.student_id as student_1_1_, student0_.student_address as student_2_1_, student0_.student_name as student_3_1_ from student student0_ where student0_.student_id>=15 Student{studentName='student1', studentId=39, studentAddress='student1Address'} Student{studentName='student2', studentId=40, studentAddress='student2Address'} Student{studentName='newStudent', studentId=42, studentAddress='newAddress'}
原因分析与解释
你的核心误解在于忽略了主键生成策略对持久化操作的影响,这和FlushModeType.COMMIT的规则无关:
IDENTITY主键生成策略的特性
Student实体的主键使用GenerationType.IDENTITY,这种策略下,主键值由数据库的自增字段生成,Hibernate无法在内存中预先生成主键。为了将生成的主键值填充到当前内存中的Student对象里,Hibernate必须立即执行INSERT语句,而不能等到事务提交时再刷新。因为如果不立即插入,后续任何需要用到该实体主键的操作(比如关联其他实体、缓存该实体)都会因为主键未赋值而失败。FlushModeType.COMMIT的生效场景
FlushModeType.COMMIT的作用是:只有在事务提交时,或者执行的查询语句会读取到当前持久化上下文中未刷新的修改数据时,才触发持久化上下文的刷新。但这个规则不适用于IDENTITY策略下的插入操作,因为这类插入是Hibernate为了获取主键而必须立即执行的强制操作,不受FlushMode的控制。验证逻辑
你可以把Student的主键生成策略改成GenerationType.SEQUENCE或者GenerationType.TABLE(这两种策略Hibernate可以在内存中生成主键值),此时再执行代码,就会看到INSERT语句在事务提交时才执行,符合你最初对FlushModeType.COMMIT的预期。
内容的提问来源于stack exchange,提问作者Abhi

