Spring Boot JPA中H2与PostgreSQL持久化行为不一致问题排查
问题:H2与PostgreSQL在Spring Boot JPA下的持久化行为不一致
在测试中使用内存H2数据库时,发现其与PostgreSQL在Spring Boot JPA下的持久化行为不一致:新增实体时,两种数据库都会为实体分配ID,但H2中查询不到新增的条目。希望让两者的持久化行为保持一致,并找出问题所在。
测试代码
System.out.println("BEFORE:"); List<FirstTable> values = myDAO.findAll(FirstTable.class); values.forEach(System.out::println); FirstTable ft = new FirstTable(); ft.setValue("added value"); myDAO.save(ft); System.out.println("AFTER SAVE:"); System.out.println(ft); System.out.println("TABLE RESULT"); List<FirstTable> values2 = myDAO.findAll(FirstTable.class); values2.forEach(System.out::println);
数据库输出对比
PostgreSQL输出
pg BEFORE: id: 1; value: 1234 id: 2; value: 2345 id: 3; value: 3456 id: 4; value: 4567 AFTER SAVE: id: 5; value: added value TABLE RESULT: id: 1; value: 1234 id: 2; value: 2345 id: 3; value: 3456 id: 4; value: 4567 id: 5; value: added value
H2输出
h2 BEFORE: id: 1; value: 1234_test id: 2; value: 2345_test id: 3; value: 3456_test id: 4; value: 4567_test AFTER SAVE: id: 5; value: added value TABLE RESULT: id: 1; value: 1234_test id: 2; value: 2345_test id: 3; value: 3456_test id: 4; value: 4567_test
H2配置
spring.datasource.driverClassName=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test;INIT=runscript from '../src/main/resources/my_dump.sql' spring.datasource.user=sa spring.datasource.password= hibernate.dialect=org.hibernate.dialect.H2Dialect #spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true spring.h2.console.path=/h2-console
MyDaoImpl的save实现
public void save(MyEntity entity) { entityManager.persist(entity); }
完整测试输出
> Task :common:compileJava > Task :app:compileJava UP-TO-DATE > Task :app:processResources UP-TO-DATE > Task :app:classes UP-TO-DATE > Task :app:compileTestJava UP-TO-DATE > Task :app:processTestResources > Task :app:testClasses > Task :common:processResources NO-SOURCE > Task :common:classes > Task :common:jar UP-TO-DATE 2024-01-12T11:57:24.032+03:00 INFO 151062 --- [ Test worker] WorkersTest : Starting WorkersTest using Java 17.0.9 with PID 151062 (started by ilys in /home/ilys/Programming/learn/testHibernate/app) 2024-01-12T11:57:24.035+03:00 INFO 151062 --- [ Test worker] WorkersTest : No active profile set, falling back to 1 default profile: "default" 2024-01-12T11:57:24.255+03:00 INFO 151062 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-01-12T11:57:24.270+03:00 WARN 151062 --- [ Test worker] o.s.b.a.AutoConfigurationPackages : @EnableAutoConfiguration was declared on a class in the default package. Automatic @Repository and @Entity scanning is not enabled. 2024-01-12T11:57:24.271+03:00 INFO 151062 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 6 ms. Found 0 JPA repository interfaces. 2024-01-12T11:57:24.446+03:00 INFO 151062 --- [ Test worker] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-01-12T11:57:24.472+03:00 INFO 151062 --- [ Test worker] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.3.1.Final 2024-01-12T11:57:24.489+03:00 INFO 151062 --- [ Test worker] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2024-01-12T11:57:24.592+03:00 INFO 151062 --- [ Test worker] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2024-01-12T11:57:25.037+03:00 INFO 151062 --- [ Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) 2024-01-12T11:57:25.058+03:00 INFO 151062 --- [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-01-12T11:57:25.357+03:00 INFO 151062 --- [ Test worker] WorkersTest : Started WorkersTest in 1.459 seconds (process running for 1.915) BEFORE: id: 1; value: 1234_test id: 2; value: 2345_test id: 3; value: 3456_test id: 4; value: 4567_test AFTER SAVE: id: 5; value: added value TABLE RESULT id: 1; value: 1234_test id: 2; value: 2345_test id: 3; value: 3456_test id: 4; value: 4567_test OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended 2024-01-12T11:57:25.837+03:00 INFO 151062 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' > Task :app:test Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0. You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. BUILD SUCCESSFUL in 3s 7 actionable tasks: 3 executed, 4 up-to-date 11:57:25: Execution finished ':app:test --tests "WorkersTest.sometest"'.
问题原因与解决方案
核心原因:事务与缓存未刷写
Hibernate的persist操作默认仅将实体加入EntityManager的一级缓存,不会立即刷写到数据库。PostgreSQL因事务特性或自动提交机制的差异,能在查询时读取到缓存中的变更;但H2在无事务上下文时,缓存变更不会同步到数据库,导致后续查询看不到新增条目。
具体解决方法
- 给测试方法添加事务注解
在测试方法上加上@Transactional,Spring会自动管理事务上下文,确保persist的变更在当前会话中生效:
@Test @Transactional public void sometest() { // 测试代码逻辑 }
如果需要测试结束后保留数据,可额外添加@Rollback(false),但一般测试用例无需保留数据。
- 手动刷写数据库缓存
在DAO的save方法中,调用entityManager.flush()强制将缓存中的变更刷写到H2数据库:
public void save(MyEntity entity) { entityManager.persist(entity); entityManager.flush(); // 强制同步到数据库 }
- 优化H2数据库URL配置
在H2的URL中添加DB_CLOSE_DELAY=-1,防止连接关闭后内存数据库被销毁,确保测试过程中数据库实例始终存在:
spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;INIT=runscript from '../src/main/resources/my_dump.sql'
- 修复实体扫描警告
日志中提示"@EnableAutoConfiguration was declared on a class in the default package",将Spring Boot启动类从默认包移到具体包(如com.example.demo),确保Spring能自动扫描@Entity和@Repository,避免潜在的映射问题。
内容的提问来源于stack exchange,提问作者mamol
相关产品推荐
相关产品推荐

