Skip to content

Add executeWithKey() support to JPAInsertClause and HibernateInsertClause #1691

@zio0911

Description

@zio0911

Important Notice

Thank you for opening an issue! Please note that, as outlined in the README, I currently only work on feature requests or bug fixes when sponsored. Balancing this project with professional and personal priorities means I have a very limited amount of effort I can divert to this project.

You must put in the work to address this issue, or it won't be addressed.

  • [V] I am willing to put in the work and submit a PR to resolve this issue.

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Motivation
QueryDSL provides a readable, type-safe query API for select, update, and delete operations in JPA projects. However, there is no way to retrieve generated keys after an insert in the JPA module. This forces developers to fall back to EntityManager.persist() + flush() just for insert + key return, breaking the consistent QueryDSL style.

The SQL module already supports executeWithKey(), but using it in a JPA project requires a separate SQLQueryFactory configuration, SQL-specific Q-classes generated from the DB schema, and managing two different query factories. This is excessive overhead just for insert key return.

Before / After
Before (current) — must break out of QueryDSL for insert + key return:

@Autowired JPAQueryFactory queryFactory;

// insert + key return → forced to use EntityManager
entityManager.persist(entity);
entityManager.flush();
Long seq = entity.getSeq();

// back to QueryDSL for update
queryFactory.update(role)
    .set(role.role, "ROLE_" + seq)
    .where(role.seq.eq(seq))
    .execute();

After (with this feature) — everything stays in QueryDSL:

@Autowired JPAQueryFactory queryFactory;

// insert + key return
Long seq = queryFactory.insert(role)
    .set(role.name, dto.roleName())
    .set(role.status, status)
    .set(role.description, dto.description())
    .set(role.createdBy, actorSeq)
    .set(role.createdAt, LocalDateTime.now())
    .executeWithKey(role.seq);

// update with generated key
queryFactory.update(role)
    .set(role.role, "ROLE_" + seq)
    .where(role.seq.eq(seq))
    .execute();

Proposed Solution
Add executeWithKey(Path) and executeWithKey(Class) to both JPAInsertClause and HibernateInsertClause.

Since JPA's Query.executeUpdate() only returns affected row count and has no API for generated keys, the implementation bypasses JPQL and executes a native SQL INSERT via JDBC with Statement.RETURN_GENERATED_KEYS:

Reads @table / @column annotations to build native SQL (same pattern as NativeSQLSerializer)
HibernateInsertClause uses Session.doReturningWork() for JDBC access
JPAInsertClause uses EntityManager.unwrap(Session.class).doReturningWork()
Limitations
INSERT ... SELECT subqueries are not supported (throws UnsupportedOperationException)
Requires explicit @table / @column annotations if using a custom Hibernate PhysicalNamingStrategy
JPAInsertClause currently relies on Hibernate as the JPA provider for JDBC connection access

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions