使用JPA和Hibernate 6在Spanner持久化数据时遇连接数超限错误
解决Spanner集成JPA/Hibernate时"连接数超过20"的错误
问题现象
持久化DemoClass实体时触发错误:
Error: Number of joins exceeds the maximum allowed limit of 20.
实体类定义
@Entity DemoClass extends CommonClass{ ... ... //getter and setter } @MappedSuperClass CommonClass implements Serializable { @CreationTimeStamp Timestamp CreatedTime; @CreationTimeStamp Timestamp updatedTime; @OneToOne Demo2 createdUser; @OneToOne Demo2 updatedUser; //getter and setter } @Entity Demo2 extends CommonClass { ... ... //getter and setter }
依赖配置
spring-boot-starter-parent: 3.3.5 spring-boot-starter-data-jpa google-cloud-spanner-hibernate-dialect: 3.7.1 google-cloud-spanner: 6.81.2 spring-cloud-gcp-data-spanner: 5.8.0 google-cloud-spanner-jdbc : 2.24.1 hibernate-types-60: 2.21.1
问题原因
核心是递归关联+EAGER加载导致的连接爆炸:
Demo2继承了CommonClass,而CommonClass中又定义了两个@OneToOne关联到Demo2- 默认情况下
@OneToOne的获取策略是FetchType.EAGER,Hibernate会自动左连接所有关联表 - 由于继承和循环关联,Hibernate生成的SQL会不断嵌套连接
Demo2表,最终超过Spanner限制的20个连接数
解决方案
1. 修改关联的加载策略为懒加载
将CommonClass中的@OneToOne关联改为懒加载,避免自动触发所有关联连接:
@OneToOne(fetch = FetchType.LAZY) Demo2 createdUser; @OneToOne(fetch = FetchType.LAZY) Demo2 updatedUser;
需要使用关联数据时,再通过EntityManager#find()或手动调用getter触发加载,这样查询主实体时不会一次性连接所有关联表。
2. 重构实体结构,消除递归关联
如果业务允许,调整继承关系或关联方式:
- 取消
Demo2对CommonClass的继承,从根源避免循环关联 - 将
createdUser和updatedUser改为存储用户ID(比如Long createdUserId),而非直接关联实体,需要时手动查询用户信息
3. 手动控制查询逻辑
避免依赖Hibernate自动生成查询,改用JPQL或原生SQL精准控制连接表数量:
// 示例JPQL:只查询DemoClass及必要的关联字段 String jpql = "SELECT d, cu.id, uu.id FROM DemoClass d LEFT JOIN d.createdUser cu LEFT JOIN d.updatedUser uu WHERE d.id = :id"; Query query = entityManager.createQuery(jpql); query.setParameter("id", demoId);
这种方式只生成必要的连接,不会触发递归的表连接。
4. 调整Hibernate抓取模式
使用@Fetch(FetchMode.SELECT)让Hibernate用单独查询加载关联,而非左连接:
@OneToOne(fetch = FetchType.LAZY) @Fetch(FetchMode.SELECT) Demo2 createdUser;
这种方式会在访问关联实体时执行单独的SELECT查询,避免主查询中产生过多连接。
内容的提问来源于stack exchange,提问作者Sandeep
相关产品推荐
相关产品推荐

