hibernate/hibernate-orm · error · SemanticException

Not expecting multiple table references for an SQM INSERT-SE

Error message

Not expecting multiple table references for an SQM INSERT-SELECT

What it means

Translating an HQL INSERT ... SELECT, Hibernate builds a root table group for the insertion target and requires it to reference exactly one table. If that table group ends up with joins (secondary tables via @SecondaryTable, JOINED inheritance pieces, secondary table group joins triggered by the target's mapped attributes), the insert cannot be rendered as a single INSERT ... SELECT and visitInsertSelectStatement throws SemanticException('Not expecting multiple table references for an SQM INSERT-SELECT').

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java:1236

			);

			getFromClauseAccess().registerTableGroup( rootPath, rootTableGroup );

			insertStatement = new InsertSelectStatement(
					cteContainer,
					(NamedTableReference) rootTableGroup.getPrimaryTableReference(),
					entityDescriptor,
					emptyList()
			);
			additionalInsertValues = visitInsertionTargetPaths(
					(assigable, references) -> insertStatement.addTargetColumnReferences( references ),
					sqmStatement,
					entityDescriptor,
					rootTableGroup
			);

			if ( hasJoins( rootTableGroup ) ) {
				throw new SemanticException( "Not expecting multiple table references for an SQM INSERT-SELECT" );
			}
		}
		finally {
			popProcessingStateStack();
			currentClauseStack.pop();
		}

		insertStatement.setSourceSelectStatement(
				visitQueryPart( selectQueryPart )
		);

		insertStatement.getSourceSelectStatement().visitQuerySpecs(
				querySpec -> {
					final boolean appliedRowNumber =
							additionalInsertValues.applySelections( querySpec, getSessionFactory() );
					// Just make sure that if we get here, a row number will never be applied
					// If this requires the special row number handling, it should use the mutation strategy
					assert !appliedRowNumber;

View on GitHub (pinned to fad1729dce)

Solutions

  1. Restructure the mapping: remove @SecondaryTable for the target entity or switch JOINED inheritance to SINGLE_TABLE (with discriminator) so one table suffices
  2. Write the insert with native SQL (session.createNativeMutationQuery) covering both tables explicitly
  3. Insert into a single-table staging/DTO entity and post-process, or perform per-row persists for the multi-table case
  4. Verify which mapping element creates the extra table reference (enable hibernate.show_sql / trace org.hibernate.SQL) and move the offending columns into the primary table

Example fix

-- before: Person has @SecondaryTable("person_details")
insert into Person (id, name, bio) select p.id, p.name, p.bio from Person p where ...
-- after: native SQL covering both tables
insert into person (id, name) select p.id, p.name from person p;
insert into person_details (id, bio) select p.id, p.bio from person p;
Defensive patterns

Strategy: fallback

Validate before calling

// Only route insert-select to HQL when target is single-table
EntityPersister persister = session.getEntityPersister(Person.class.getName(), null);
boolean singleTable = persister.getPropertySpans() <= 1 || !persister.hasSubclasses();
// heuristic: check factory metadata for secondary tables before choosing HQL vs native SQL

Type guard

static boolean singleTableTarget(SessionFactory sf, String entityName) {
    var descr = sf.getMetamodel().getEntityDescriptor(entityName);
    return descr.getTableReferences() == null || descr.getPropertySpan() == descr.getTableReferences().size();
}

Try / catch

catch (SemanticException e) { if (e.getMessage().contains("multiple table references")) { /* fall back to native multi-table INSERT or per-row persist */ } else throw e; }

Prevention

When it happens

Trigger: HQL 'insert into Person (id, name) select ... from Person p' where Person is mapped with @SecondaryTable or JOINED inheritance so createRootTableGroup produces joined table references; inserting into an entity whose target paths (visitInsertionTargetPaths) force joins to other tables; entities with @OneToMany join-table style secondary mappings referenced in the target column list.

Common situations: Bulk-copy/ETL jobs using HQL insert-select on entities with secondary tables; testing insert-select against a simpler entity worked, then pointing it at a richer mapped entity; inheritance refactoring (SINGLE_TABLE to JOINED) breaking existing insert-select statements; Hibernate 5 -> 6/7 migration where such statements previously rendered with different table-group logic.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/b3d5130440771a7e. Report an issue: GitHub.