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