skip to content

A raw SQL query returns one row per order joined with the customer row and a computed total. In JPA, how do you declare a @SqlResultSetMapping so that each row arrives as two entities plus a scalar, or as a DTO instead of an Object[]?

level: middleimportance: must knowfreq 44%

answer

  1. Three parts: entities / columns / classes
  2. @FieldResult reconnects attribute → SQL alias
  3. @EntityResult still needs every mapped column
  4. @ColumnResult type pins driver-dependent numerics
  5. Mapping names are global, resolved at boot

basics

~20 s

Declare a @SqlResultSetMapping naming the pieces of each row: @EntityResult (with @FieldResult when column aliases differ from the mapped names) for entities, @ColumnResult for scalars, and @ConstructorResult to build a DTO from listed columns. Pass its name to createNativeQuery(sql, "MappingName").

solid answer

~50 s

```java @SqlResultSetMapping(name = "OrderWithCustomerAndTotal", entities = { @EntityResult(entityClass = Order.class, fields = { @FieldResult(name = "id", column = "o_id"), @FieldResult(name = "placedAt", column = "o_placed_at") }), @EntityResult(entityClass = Customer.class, fields = { @FieldResult(name = "id", column = "c_id"), @FieldResult(name = "name", column = "c_name") }) }, columns = @ColumnResult(name = "total", type = BigDecimal.class)) ``` Each row is then `Object[] { Order, Customer, BigDecimal }` — the entities **managed**, in the declared order, scalars after entities. For a flat read model, swap the entities for a constructor: ```java @SqlResultSetMapping(name = "OrderSummary", classes = @ConstructorResult(targetClass = OrderSummary.class, columns = { @ColumnResult(name = "o_id", type = Long.class), @ColumnResult(name = "c_name"), @ColumnResult(name = "total", type = BigDecimal.class) })) ``` `@FieldResult` exists because a join aliases columns to avoid `id` colliding with `id`. `type` on `@ColumnResult` pins driver-dependent numeric types. Mapping names live in a global namespace and are resolved at startup, even though the SQL itself is not.

go deeper

for a junior

Know that @SqlResultSetMapping turns native rows into entities, scalars or DTOs, and that you pass its name to createNativeQuery.

for a middle

Write the annotation correctly, explain @FieldResult and the alias problem in joins, and choose @ConstructorResult for read-only projections.

for a senior

Add the operational detail — driver-dependent scalar types, entities arriving managed with all the persistence-context consequences, and mapping-name resolution at boot versus unvalidated SQL.

for a principal

Decide the codebase convention: read models as DTO constructor results by default, entity results reserved for write paths, and where mappings live so they stay reviewable.

## The problem being solved A native query returns a flat `ResultSet`. Without help, JPA hands you `Object[]` per row and you index by position — brittle, untyped, and useless if you wanted managed entities. `@SqlResultSetMapping` is a declarative description of how to cut each row into objects. It has exactly three kinds of parts, and a mapping may combine them: - `entities` — one or more `@EntityResult`, each hydrating a managed entity. - `columns` — `@ColumnResult` entries, plain scalar values. - `classes` — `@ConstructorResult` entries, each invoking a constructor with listed columns. The mapping is declared on any entity class (or in `orm.xml`) and referenced by name; names are **global to the persistence unit**, like named queries, so the `Entity.purpose` convention applies. The name is resolved when the persistence unit boots, so a typo in the mapping name is a startup failure even though the SQL body is never validated. ## @EntityResult and the alias problem ```java @EntityResult(entityClass = Order.class) ``` By default the provider looks in the `ResultSet` for the columns the entity maps, by their mapped names, and requires **all of them** — including the foreign-key columns of eager to-one associations and the discriminator column for an inheritance hierarchy. Miss one and hydration fails. The moment you join two tables, plain names break down: both `order` and `customer` have `id`, and a `ResultSet` with two `id` columns cannot be addressed by name. Hence aliasing in the SQL (`o.id as o_id`) and `@FieldResult` to reconnect each **entity attribute** to its **result column**: ```java @FieldResult(name = "placedAt", column = "o_placed_at") ``` `name` is the Java attribute path; `column` is the SQL alias. If you supply any `@FieldResult`s you are generally expected to supply them for all mapped attributes of that entity, so full-alias mappings get verbose — one of the honest reasons teams prefer constructor results. Entities produced this way are ordinary managed entities: identity-mapped, dirty-checked, and subject to the rule that an already-managed instance with the same id wins over the freshly read values. `@EntityResult` also accepts a `discriminatorColumn` for hierarchies so the provider knows which subclass to instantiate. ## @ColumnResult and types ```java columns = { @ColumnResult(name = "total", type = BigDecimal.class) } ``` `name` is the column label in the result set. `type` is optional but frequently load-bearing: the JDBC type of an aggregate varies by engine and driver — `count(*)` may arrive as `BigInteger`, `Long` or `BigDecimal` — and a `ClassCastException` in production is the usual way people learn this. Declaring `type` asks the provider to convert, which both documents intent and removes the driver dependence. ## @ConstructorResult ```java @ConstructorResult(targetClass = OrderSummary.class, columns = { @ColumnResult(name = "o_id", type = Long.class), @ColumnResult(name = "c_name"), @ColumnResult(name = "total", type = BigDecimal.class) }) ``` The provider looks for a constructor on `OrderSummary` whose parameters match, **in the listed column order**, the resolved column types, and invokes it per row. The target class does not need to be an entity or be known to JPA at all; a plain class or record with a matching constructor is enough. Mismatch in order or type is a runtime failure, which is why pinning `type` matters more here than anywhere else. This is usually the right default for read models. Nothing enters the persistence context, so there is no dirty-check snapshot, no memory for a second copy of state, no risk of an accidental UPDATE, and no requirement to select every mapped column — you select exactly what the screen or the report needs. ## Mixing A single mapping can declare entities, columns and classes together. Each row then arrives as an `Object[]` holding the results in declaration order — entities first, then constructor results and scalars in the order the mapping lists them. Read the mapping to know the positions; that coupling is the price of the flat array. ## Practical guidance - **Alias every column** in a multi-table native query, then map by alias. It costs a line and removes a whole class of ambiguity. - **Prefer `@ConstructorResult`** unless you specifically need managed entities you intend to modify. - **Pin `type` on numeric and temporal `@ColumnResult`s** to insulate against driver differences. - **Name mappings deliberately** — global namespace, so `Order.withCustomerAndTotal` beats `mapping1`. - **Declare mappings in `orm.xml`** if the annotation block on the entity is dwarfing the entity itself; the XML form has the same structure. The alternative Hibernate offers on its own `NativeQuery` API is programmatic (`addEntity`, `addScalar`, `addRoot`), which is handy for one-offs but loses the reusability of a named, startup-resolved mapping.

  • Why is @FieldResult necessary at all — cannot the provider match columns by the entity's mapped column names?
    It can, and does, when the names are unambiguous. A join breaks that: two tables each contribute an id (and often a name or created_at), and a flat ResultSet cannot be addressed by a duplicated label. You alias the columns in the SQL and use @FieldResult to say which alias feeds which entity attribute. It is also what lets a native query whose column names differ from the mapping still hydrate an entity.
  • When would you choose @ConstructorResult over @EntityResult for the same query?
    Whenever the rows are read-only output — a report, a list screen, an export. A constructor result skips the persistence context entirely, so there is no dirty-check snapshot, no identity-map interaction and no accidental UPDATE, and you may select only the columns you need instead of every mapped column. Use @EntityResult when the caller must modify the objects or navigate their associations.
  • A mapping's @ColumnResult omits type and the code casts the value to Long, which works on one database and throws ClassCastException on another. What is going on?
    The scalar type comes from the JDBC driver's result-set metadata, and engines differ on what an aggregate or an integral column produces — BigInteger, Long and BigDecimal are all common. Without a declared type the provider hands back whatever the driver produced. Setting type on @ColumnResult makes the provider convert to the type you want, which fixes the portability problem and documents the intent.

saying these in an interview costs you the question

  • Assuming @EntityResult can hydrate an entity from a partial column list
  • Skipping SQL aliases in a multi-table native query and expecting the provider to disambiguate duplicate id columns
  • Believing @ConstructorResult matches constructor parameters by column name rather than by declared order
  • Casting native aggregate results to a specific numeric type without declaring type on @ColumnResult
  • Thinking the result-set mapping name is scoped to the entity class it is declared on

context