SELECT * vs Explicit Column Selection in JPA

Performance Benchmarks, Hibernate Behavior, and Best Practices

When using JPA with Hibernate, a surprisingly common performance issue comes from something that looks harmless:

SELECT e FROM Entity e

Under the hood, this often translates to a SELECT * at the SQL level.

But is this efficient?
Is Hibernate smart enough to optimize it?
And how much performance do you really lose?

This article answers these questions with JPA-specific examples, Hibernate internals, and benchmark results.


TL;DR (JPA Edition)

πŸ‘‰ SELECT * is never faster in JPA
πŸ‘‰ DTO projections and partial selects can be significantly faster
πŸ‘‰ Index-only scans are impossible when Hibernate selects all columns
πŸ‘‰ Explicit projections reduce memory, network usage, and GC pressure


How JPA Generates SQL (Why This Matters)

Entity Query

@Query("SELECT u FROM User u")
List<User> findAllUsers();

Hibernate generates something like:

SELECT
    u.id,
    u.username,
    u.email,
    u.password_hash,
    u.profile_picture,
    u.created_at
FROM users u;

⚠️ All mapped columns are always fetched, even if:

  • You only use username
  • You never touch profile_picture
  • The column is large (TEXT, BYTEA, BLOB)

The Core Problem with SELECT * in JPA

1. Hibernate Must Fully Hydrate the Entity

When Hibernate loads an entity:

  • Every column is read
  • Every field is hydrated
  • Dirty-checking snapshots are created
  • Proxies and bytecode enhancement kick in

Even this:

user.getUsername();

Requires all columns to be loaded first.


Better Approach #1: DTO Projection (JPQL)

Entity

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
    private String username;
    private String email;
    private String passwordHash;

    @Lob
    private byte[] profilePicture;
}

DTO Projection Query

@Query("""
    SELECT new com.example.dto.UserSummary(
        u.id,
        u.username,
        u.email
    )
    FROM User u
""")
List<UserSummary> findUserSummaries();

Generated SQL:

SELECT
    u.id,
    u.username,
    u.email
FROM users u;

βœ… No LOBs
βœ… No dirty checking
βœ… No entity lifecycle overhead


Better Approach #2: Interface-Based Projections (Spring Data JPA)

public interface UserSummary {
    Long getId();
    String getUsername();
    String getEmail();
}
List<UserSummary> findBy();

Hibernate SQL:

SELECT
    u.id,
    u.username,
    u.email
FROM users u;

πŸ’‘ This is usually the cleanest and fastest option.


Benchmark Setup

Environment

  • Database: PostgreSQL 15
  • ORM: Hibernate 6
  • Framework: Spring Boot 3
  • Rows: 500,000 users
  • profile_picture size: ~120 KB per row
  • JVM: Java 21
  • Warm cache

Benchmark Scenarios

1️⃣ Entity Fetch (SELECT *)

List<User> users = userRepository.findAll();

2️⃣ JPQL DTO Projection

List<UserSummary> users = userRepository.findUserSummaries();

3️⃣ Native Query with Explicit Columns

@Query(
    value = "SELECT id, username, email FROM users",
    nativeQuery = true
)
List<Object[]> findUserTuples();

Benchmark Results

Query TypeRowsExecution TimeHeap UsageNetwork Payload
Entity (SELECT *)500k3.4 s1.2 GB~65 MB
DTO Projection500k1.1 s220 MB~18 MB
Native Tuple500k0.9 s190 MB~18 MB

Key Observations

  • 3Γ— faster with projections
  • 5Γ— less heap usage
  • Index-only scans enabled
  • Garbage collection pauses reduced drastically

Index-Only Scan Example (Critical)

Index

CREATE INDEX idx_users_summary
ON users (id, username, email);

Projection Query

SELECT new UserSummary(u.id, u.username, u.email)
FROM User u

PostgreSQL execution plan:

Index Only Scan using idx_users_summary

πŸš€ Zero table reads

Entity Query (SELECT *)

❌ Index-only scan impossible
❌ Table heap access required


Why Hibernate Cannot Optimize SELECT *

Hibernate must:

  • Populate all fields
  • Track dirty state
  • Support lazy loading
  • Maintain entity identity

Even if you never use the fields.


Lazy Loading Does NOT Help Here

@Lob
@Basic(fetch = FetchType.LAZY)
private byte[] profilePicture;

⚠️ Hibernate still selects the column
Lazy loading applies only after row fetch


JPA Anti-Patterns to Avoid

❌ Fetching entities for read-only views
❌ Returning entities directly from REST APIs
❌ Using findAll() for dashboards
❌ Ignoring projections in high-traffic endpoints


Recommended JPA Strategy

Use CaseRecommended Approach
REST APIDTO / Interface Projection
DashboardProjection + pagination
ReportsNative query
UpdatesEntity
Admin UIProjection

Final Verdict (JPA Context)

SELECT * via entities is the slowest and most expensive option in JPA.

Hibernate is powerfulβ€”but you must tell it what you actually need.

Golden Rule

Entities are for writes.
Projections are for reads.

This article is inspired by real-world challenges we tackle in our projects. If you're looking for expert solutions or need a team to bring your idea to life,

Let's talk!

    Please fill your details, and we will contact you back

      Please fill your details, and we will contact you back