Grails配置延迟加载解决MySQL联表超61张表限制问题
我有一组继承自Parent表的子表,现在遇到了MySQL联表数量限制的问题,触发了如下SQL异常:
Too many tables; MySQL can only use 61 tables in a join. Stacktrace follows: java.sql.SQLException: Too many tables; MySQL can only use 61 tables in a join at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3933) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3869) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2524) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2675) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2465) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1915) at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2023)
问题
如何在查询Parent表时避免关联所有子表?类似为属性设置lazy: true,是否存在全局配置实现所有关联延迟加载的方式?
相关实体类代码示例
class Parent { long id String name DisplayMode layoutMode // ... 其他属性 static mappings = { cache: true version: true } } class Child1 extends Parent { long id String prop1 String prop2 // ... 其他属性 static mappings = { cache: true version: true } } class Child2 extends Parent { long id String prop1 String prop2 // ... 其他属性 static mappings = { cache: true version: true } } class Child3 extends Parent { long id String prop1 String prop2 // ... 其他属性 static mappings = { cache: true version: true } }
目前已有60+个子表,Parent类是仪表盘父类,子类对应各类小部件,后续还会新增更多子类。查询Parent表时会关联所有子表,需解决该问题。使用版本:Grails 2.4.3,MySQL 8。
解决方案
1. 切换继承映射策略为单表继承(Table Per Hierarchy)
Grails默认采用表每子类策略,查询父类时会自动关联所有子表,这就是触发MySQL联表限制的核心原因。切换为单表继承后,所有子类数据存储在父表中,通过鉴别器字段区分类型,彻底避免多表关联。
在Parent类的mapping中配置:
class Parent { // ... 属性 static mappings = { tablePerHierarchy true // 启用单表继承 cache true version true discriminator column: 'widget_type', value: 'parent' // 自定义鉴别器列和父类标识值 } }
每个子类需指定对应的鉴别器值:
class Child1 extends Parent { // ... 属性 static mappings = { discriminator value: 'child1' cache true version true } }
优点:查询效率极高,无联表数量限制;缺点:父表会包含所有子类字段,可能存在较多空值,且子类字段不能重名冲突。
2. 启用子类关联的延迟加载
如果必须保留表每子类策略,可以通过配置让Grails延迟加载子类数据,仅在需要访问子类特有属性时,才单独查询对应子表。
局部配置(逐个子类设置)
在Parent类的mapping中为每个子类配置延迟加载:
class Parent { // ... 属性 static mappings = { cache true version true child1 lazy: true child2 lazy: true // ... 其他子类依次配置 } }
缺点是新增子类时需要手动补充配置,灵活性较差。
全局配置(所有子类默认延迟加载)
在项目的Config.groovy中添加全局配置,让所有继承关联默认启用延迟加载:
grails.gorm.default.mapping = { subclass(lazy: true) }
这样查询Parent时只会读取父表数据,当访问子类属性时才会触发对应子表的查询,不会一次性关联所有60+个子表。
3. 自定义查询,仅查询父表字段
如果业务场景中不需要子类的属性,可以直接用HQL或Criteria编写仅查询父表字段的语句,完全跳过子表关联:
HQL示例:
def parents = Parent.executeQuery("select p from Parent p")
Criteria示例:
def parents = Parent.createCriteria().list { projections { property('id') property('name') property('layoutMode') // ... 按需选择父类需要的属性 } }
内容的提问来源于stack exchange,提问作者JiniKJohny

