Skip to content

JPAInsertClause/HibernateInsertClause .set() style produces invalid JPQL #1718

@zio0911

Description

@zio0911
  • I am willing to put in the work and submit a PR to resolve this issue.

Description

When using the set(Path, value) style on JPAInsertClause (or HibernateInsertClause) instead of columns().values(), the JPQL produced by JPQLSerializer.serializeForInsert is malformed. Hibernate then rejects it with SyntaxException.

Reproduction

QCat cat = QCat.cat;
new JPAInsertClause(em, cat)
    .set(cat.name, "Bobby")
    .set(cat.alive, false)
    .execute();

Actual JPQL emitted

insert into Cat (name, alive)
cat.name = ?1, cat.alive = ?2

This is not valid JPQL/HQL. After the column list, Hibernate expects VALUES (...) or a sub-select, but the serializer emits path = value pairs (a SET-style assignment that JPQL/HQL does not support).

Expected JPQL

insert into Cat (name, alive)
values (?1, ?2)

Stack trace

org.hibernate.query.SyntaxException: At 2:0 and token 'cat', mismatched input 'cat',
expecting one of the following tokens: '(', FROM, ORDER, SELECT, VALUES, WHERE, WITH
[insert into Cat (name, alive)
cat.name = ?1, cat.alive = ?2]
at org.hibernate.query.hql.internal.StandardHqlTranslator$1.syntaxError(StandardHqlTranslator.java:124)
...
at com.querydsl.jpa.impl.JPAInsertClause.execute(JPAInsertClause.java:84)

Root cause

JPQLSerializer.serializeForInsert (querydsl-jpa) handles the inserts map (the set() style) by emitting path = value pairs:

} else if (inserts != null && !inserts.isEmpty()) {
  first = true;
  for (Map.Entry<Path<?>, Expression<?>> entry : inserts.entrySet()) {
    if (!first) append(", ");
    handle(entry.getKey());
    append(" = ");
    handle(entry.getValue());
    first = false;
  }
}

JPQL/HQL INSERT only supports the VALUES form and the ... SELECT form. There is no SET form. The serializer should convert the inserts map to a VALUES (...) list of values in the same order as the column list it just emitted.

Suggested fix

Replace the SET-style emission in JPQLSerializer.serializeForInsert with a VALUES list:

} else if (inserts != null && !inserts.isEmpty()) {
  append(VALUES);
  append(" (");
  first = true;
  for (Map.Entry<Path<?>, Expression<?>> entry : inserts.entrySet()) {
    if (!first) append(", ");
    handle(entry.getValue());
    first = false;
  }
  append(")");
}

The column list (already emitted earlier from inserts.keySet()) and the value list will line up by iteration order of the LinkedHashMap used by JPAInsertClause/HibernateInsertClause.

Affected

  • com.querydsl.jpa.impl.JPAInsertClause#execute (whenever any set(...) is used)
  • com.querydsl.jpa.hibernate.HibernateInsertClause#execute (same)
  • Any caller relying on set()-style JPA INSERT

columns().values() style and INSERT ... SELECT (subQuery) are unaffected.

Environment

  • querydsl-jpa: 7.x (current master)
  • Hibernate: 6.x
  • Java: 17+

Workaround

Use columns(...).values(...) instead of repeated set(path, value) calls.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions