bigywater.tech
Spring · 153

Hibernate 7 随 Boot 4 升级后,哪些行为变了

Hibernate 7 随 Spring Boot 4 一起上来了。升级之后表结构会变吗?HQL 结果会变吗?哪些写法编译不过?这篇在 MySQL 上用同一份实体对比 Hibernate 6.6 与 7.2。

0 个表有差异
同一份实体在两个版本上 ddl-auto=create 生成的 6 张表,show create table 逐表对比的差异数
21 / 22
同一批 HQL 在 6.6 与 7.2 上结果一致的条数
15 / 24
24 种 Hibernate 专有写法里 7.2 能编译的个数(6.6 是 20 / 24)

DDL

实测 · 本机执行输出
  3.5.16:启动成功
    表:['child', 'child_seq', 'item', 'item_seq', 'parent', 'parent_seq']
    生成的 DDL 语句数(日志里 create/alter/drop 开头的 Hibernate: 行):14
  4.0.8:启动成功
    表:['child', 'child_seq', 'item', 'item_seq', 'parent', 'parent_seq']
    生成的 DDL 语句数(日志里 create/alter/drop 开头的 Hibernate: 行):14
  逐表逐列对比 show create table(只列出两个版本不同的行):
  有差异的表 0 个
  item 表的列定义(两个版本一样):
    `active` bit(1) NOT NULL
    `day` date DEFAULT NULL
    `dur` decimal(21,0) DEFAULT NULL
    `ord` tinyint DEFAULT NULL
    `price` decimal(38,2) DEFAULT NULL
    `qty` int DEFAULT NULL
    `ratio` double DEFAULT NULL
    `created` datetime(6) DEFAULT NULL
    `id` bigint NOT NULL
    `off` datetime(6) DEFAULT NULL
    `seen` datetime(6) DEFAULT NULL
    `zoned` datetime(6) DEFAULT NULL
    `uid` binary(16) DEFAULT NULL
    `name` varchar(255) DEFAULT NULL
    `data` varbinary(255) DEFAULT NULL
    `big` longtext
    `label` enum('GREEN','RED') DEFAULT NULL
    PRIMARY KEY (`id`)
    CONSTRAINT `item_chk_1` CHECK ((`ord` between 0 and 1))

两个版本生成的表和 DDL 完全一致。

旧库

实测 · 本机执行输出
  4.0.8 + validate 对着 3.5.16 建的库:启动成功,没有报错
  4.0.8 + update 对着 3.5.16 建的库:启动成功;执行的 DDL 语句 0 条
  update 之后各表的 show create table 是否有变化:False(对比的是 update 之前与之后)

3.5.16 建的库,4.0.8 validate 通过、update 不执行 DDL。

分页

实测 · 本机执行输出
  3.5.16:join fetch + setMaxResults(2):返回 2 个 Parent,各自 children 数 [2, 2];日志:o.h.orm.query - HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
  4.0.8:join fetch + setMaxResults(2):返回 2 个 Parent,各自 children 数 [2, 2];日志:o.h.orm.query - HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
  3.5.16 + hibernate.query.fail_on_pagination_over_collection_fetch=true:join fetch + setMaxResults(2):异常 HibernateException → HibernateException: setFirstResult() or setMaxResults() specified with collection fetch join (in-memory pagination was about to be applied, but 'hibernate.query.fail_on_pagination_over_collection_fetch' is enabled)
  4.0.8 + hibernate.query.fail_on_pagination_over_collection_fetch=true:join fetch + setMaxResults(2):异常 HibernateException → HibernateException: setFirstResult() or setMaxResults() specified with collection fetch join (in-memory pagination was about to be applied, but 'hibernate.query.fail_on_pagination_over_collection_fetch' is enabled)

join fetch 加分页都是内存分页并警告,可以用 fail_on_pagination_over_collection_fetch 变成抛异常。

HQL

实测 · 本机执行输出
  相同:select count(i) from Item i → 1 行,首行 Long 3
  相同:select i.name from Item i where i.id = ?1  [参数 Long 1] → 1 行,首行 String a
  相同:select i.name from Item i where i.id = ?1  [参数 Integer 1] → 1 行,首行 String a
  相同:select i.name from Item i where i.id = ?1  [参数 String 1] → 1 行,首行 String a
  相同:select i.name from Item i where i.price > :p  [参数 Integer 1] → 3 行,首行 String a
  不同:select i.name from Item i where i.price > :p  [参数 String abc]
      6.6 → 异常 IllegalArgumentException → NumberFormatException: For input string: "abc"
      7.2 → 异常 QueryArgumentException: Argument to query parameter has an incompatible type (java.lang.String is not assignable to java.math.BigDecimal)
  相同:select i.name from Item i where i.id in :ids  [参数 空 List] → 0 行,首行 null
  相同:select i.name from Item i where i.ord = 'RED' → 异常 IllegalArgumentException → SemanticException: Cannot compare left expression of type 'demo.Color' with right expression of type 'java.lang.String'
  相同:select i.name from Item i where i.label = 'GREEN' → 3 行,首行 String a
  相同:select i.name from Item i order by i.id desc limit 2 → 2 行,首行 String c
  相同:select i.name from Item i where i.name = ? → 异常 IllegalArgumentException → ParameterLabelException: Unlabeled ordinal parameter ('?' rather than ?1)
  相同:select i.qty / 2 from Item i where i.name = 'a' → 1 行,首行 Integer 2
  相同:select i.qty // 2 from Item i where i.name = 'a' → 异常 IllegalArgumentException → SyntaxException: At 1:14 and token '/', no viable alternative at input 'select i.qty /*/ 2 from Item i where i.name = 'a
  相同:select max(i.id) + 1.5 from Item i → 1 行,首行 Double 4.5
  相同:select sum(i.qty) from Item i → 1 行,首行 Long 21
  相同:select avg(i.qty) from Item i → 1 行,首行 Double 7.0
  相同:select cast(i.id as String) from Item i where i.name = 'a' → 1 行,首行 String 1
  相同:select i.qty * 1.0 from Item i where i.name = 'a' → 1 行,首行 Double 5.0
  相同:select i.name from Item i where i.name = null → 0 行,首行 null
  相同:select i.name from Item i where i.name like 'a%' escape '!' → 1 行,首行 String a
  相同:select i from Item i where i.id = 1 and i.big is null → 1 行,首行 Item Item#1
  相同:from Item where qty > 1 → 3 行,首行 Item Item#1
  共 22 条,结果不同的 1 条

22 条里 21 条一致,仅参数类型不匹配的异常类型不同。

API

实测 · 本机执行输出
  3.5.16 + hibernate.dialect=org.hibernate.dialect.MySQL8Dialect:启动成功
  4.0.8 + hibernate.dialect=org.hibernate.dialect.MySQL8Dialect:启动失败 Caused by: java.lang.ClassNotFoundException: Could not load requested class: org.hibernate.dialect.MySQL8Dialect
  3.5.16 + hibernate.dialect=org.hibernate.dialect.MySQLDialect:启动成功
  4.0.8 + hibernate.dialect=org.hibernate.dialect.MySQLDialect:启动成功
  Hibernate 专有写法编译:6.6 通过 20 / 24;7.2 通过 15 / 24
    org.hibernate.annotations.Where:6.6→通过;7.2→失败(cannot find symbol)
    org.hibernate.annotations.Type(type=…):6.6→失败(cannot find symbol);7.2→失败(cannot find symbol)
    org.hibernate.annotations.Proxy:6.6→通过;7.2→失败(cannot find symbol)
    org.hibernate.criterion.Restrictions(旧 Criteria):6.6→失败(package org.hibernate.criterion does not);7.2→失败(package org.hibernate.criterion does not)
    org.hibernate.Criteria:6.6→失败(cannot find symbol);7.2→失败(cannot find symbol)
    Session#save(Object):6.6→通过;7.2→失败(cannot find symbol)
    Session#saveOrUpdate(Object):6.6→通过;7.2→失败(cannot find symbol)
    org.hibernate.dialect.MySQL8Dialect:6.6→通过;7.2→失败(cannot find symbol)
    org.hibernate.dialect.PostgreSQL95Dialect:6.6→失败(cannot find symbol);7.2→失败(cannot find symbol)

MySQL8Dialect、@Where、@Proxy、Session.save、saveOrUpdate 在 7.2 里没有了。

分步演示

Hibernate 7
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Boot 3.5.16:Hibernate 6.6.53 与 Spring Data JPA 3.5.13;Spring Boot 4.0.8:Hibernate 7.2.24 与 Spring Data JPA 4.0.7;数据库是本机自己起的 MySQL 实例,用 mysql-connector-j,两个版本各用一个库;实体包括 Item、Parent、Child;「专有写法」每种是一个独立的 .java 文件,逐个 javac 编译)
    • 没有验证:PostgreSQL、Oracle 等其他数据库上的 DDL 与方言;Spring Data JPA 派生查询、Specification、@Query 在 4.x 里的变化;二级缓存、批量抓取(batch fetch)、事件监听器的行为;Hibernate 7 新增特性(例如 @Find、Jakarta Persistence 3.2 的新 API);性能差异;实体较多、继承与复合主键等复杂映射下的 DDL 差异;ddl-auto 以外的迁移工具(Flyway)与 7.2 的交互
    京ICP备2026067502号-1京公网安备11010502063487号