Biggest production mistake: assuming default fetch strategies are optimal — N+1 queries kill performance silently.
Always monitor generated SQL in production; tools like datasource-proxy or p6spy expose hidden queries.
✦ Definition~90s read
What is Hibernate ORM Basics?
Hibernate ORM is a mature, battle-tested object-relational mapping framework for Java that maps Java objects to database tables, eliminating the impedance mismatch between OOP and relational models. It implements JPA (Jakarta Persistence API) as a reference implementation, but offers additional features like native SQL querying, second-level caching, and dirty-checking that go beyond the spec.
★
Imagine you have a filing cabinet full of paper forms (your database), but you work entirely with sticky notes on your desk (Java objects).
Hibernate exists because hand-writing JDBC code for CRUD operations is error-prone, verbose, and doesn't scale—especially when dealing with complex entity graphs, lazy loading, or transactional consistency. It's the default ORM for Spring Boot and powers thousands of production systems, but it's not a silver bullet: if you need massive batch operations, raw SQL with bulk updates, or a schema-less data store, Hibernate's overhead can hurt.
The N+1 problem it famously introduces—where lazy loading triggers one query per child entity—is a direct consequence of its proxy-based loading strategy, which can silently kill performance in payment services or any latency-sensitive system. Understanding Hibernate means knowing when to use JOIN FETCH, @EntityGraph, or batch fetching to avoid that trap, and recognizing that its session and transaction management (with first-level cache scoped to the session) is both a convenience and a footgun if you don't flush or clear correctly.
Plain-English First
Imagine you have a filing cabinet full of paper forms (your database), but you work entirely with sticky notes on your desk (Java objects). Every time you want to save or retrieve something, someone has to manually copy between the two formats — that's exhausting and error-prone. Hibernate is like hiring a super-organised assistant who automatically keeps your sticky notes and filing cabinet perfectly in sync. You write on your sticky note, and the assistant handles all the filing — no manual copying required.
Every non-trivial Java application needs persistent data. You need users to stay logged in tomorrow, orders to survive a server restart, and product catalogs to outlive a JVM. The default solution — writing raw JDBC SQL — turns into hundreds of lines of boilerplate: open connection, prepare statement, map ResultSet columns to fields, close connection, handle exceptions at every step. It's repetitive, fragile, and a maintenance nightmare the moment your schema changes. Hibernate was built to solve exactly that pain, and it's been the most widely deployed Java persistence framework for over two decades for good reason.
At its core, Hibernate is an Object-Relational Mapping (ORM) library. It allows you to express database interactions in the language of Java objects, abstracting away the 'Impedance Mismatch'—the conceptual difference between the nested, circular nature of objects and the flat, tabular nature of relational databases.
What Hibernate ORM Actually Does — And Why It Matters
Hibernate ORM is a Java framework that maps Java objects to relational database tables, automating the translation between object-oriented code and SQL. The core mechanic is the Session, which tracks every loaded entity and flushes changes to the database at transaction commit. This abstraction hides SQL, but it also introduces a critical performance trap: lazy loading. When you access a relationship that wasn't fetched, Hibernate issues a new SQL query on the spot — one per accessed entity. That's the N+1 problem: one query for the parent, then N queries for each child. In a payment service with 10,000 transactions, that's 10,001 queries instead of one JOIN. The key property is that lazy loading is the default for collections (OneToMany, ManyToMany). You must explicitly choose fetch strategies — JOIN, batch, or subselect — or you pay the price in latency. Use Hibernate when you need automatic dirty checking, optimistic locking, and a unit-of-work pattern. Avoid it for read-heavy, high-throughput systems unless you control every query path. In production, a single N+1 can collapse a database connection pool under moderate load.
⚠ Lazy Loading Is Not Free
Lazy loading defers the query, not the cost. The database still executes N+1 queries — just later, often in a hot loop, amplifying the damage.
📊 Production Insight
A payment reconciliation service loaded 5,000 transactions with lazy @OneToMany. Each transaction triggered 3 additional queries for line items, fees, and status history — 15,001 queries per batch. The PostgreSQL connection pool (max 50) saturated in under 2 seconds, causing cascading timeouts across all downstream services. Rule: always profile with a database query log in staging before any release that touches entity relationships.
🎯 Key Takeaway
Lazy loading is the default for collections — you must override it explicitly.
One N+1 in a hot path can exhaust a connection pool in seconds.
Always use JOIN FETCH or @BatchSize for any relationship accessed in a loop.
thecodeforge.io
Hibernate Orm Basics
Entity Mapping and JPA Annotations
Mapping a Java class to a database table is done via annotations in the jakarta.persistence package. The @Entity annotation marks the class as a database entity. @Table lets you specify the table name, schema, and indexes. Each field or getter can be mapped with @Column to define column name, nullability, length, and precision. Relationships are defined with @OneToMany, @ManyToOne, @OneToOne, and @ManyToMany. The @JoinColumn specifies the foreign key column.
A common mistake is to omit the @Column(nullable = false) on fields that must be present — Hibernate will allow them to be null, leading to unexpected NullPointerException when the data is loaded from the database. Always match the database constraints exactly.
In io.thecodeforge services, we always validate that the @Column annotation's nullable and length attributes mirror the DDL. This catches schema mismatches at compile time when using tools like Hibernate's schema validation.
Think of JPA annotations as dual-contracts: they tell Hibernate how to generate DDL and how to map at runtime.
@Table and @Column shape the schema (DDL generation).
@ManyToOne and @OneToMany define SQL join patterns (DML).
Mismatch between annotation and actual DDL causes runtime errors or silent data corruption.
Use spring.jpa.hibernate.ddl-auto=validate in production to detect mismatches early.
📊 Production Insight
Using FetchType.EAGER on every relationship is the fastest way to degrade performance — Hibernate loads the entire graph even if you only need one entity.
Always default to FetchType.LAZY and use JOIN FETCH or @EntityGraph for read optimisations.
In production, enable schema validation to catch drift between entities and actual database schema.
🎯 Key Takeaway
Annotations are not documentation — they are code that generates SQL.
fetch = LAZY is the safe default; eager loading should be explicit per query.
Validate schema in production to prevent silent column mismatch.
Session Lifecycle and Transaction Management
Hibernate's Session (or JPA's EntityManager) is a lightweight, single-threaded object that represents a unit of work. It wraps a JDBC connection and maintains a persistence context — a cache of managed entities. The lifecycle of an entity moves through states: Transient (not associated with a Session), Persistent (in the session and tracked for changes), Detached (was persistent but session closed).
Transactions are mandatory for any write operation. In a framework like Spring, @Transactional handles open/commit/rollback. Without it, every persist(), merge(), or delete() will throw TransactionRequiredException. The most common production failure is forgetting to set the propagation and isolation level, leading to dirty reads or lost updates.
In io.thecodeforge, we always configure a PlatformTransactionManager and use declarative transactions with explicit rollback rules. Avoid exception swallowing — if a checked exception occurs, mark the transaction for rollback with @Transactional(rollbackFor = Exception.class).
io/thecodeforge/service/OrderService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package io.thecodeforge.service;
import io.thecodeforge.persistence.Order;
import io.thecodeforge.persistence.OrderRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ServicepublicclassOrderService {
privatefinalOrderRepository orderRepository;
publicOrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional(rollbackFor = Exception.class, timeout = 30)
publicOrdercreateOrder(Order order) {
// All DB operations share one session and transactionOrder saved = orderRepository.save(order);
// If any exception occurs here, the whole transaction rolls backsendConfirmationEmail(saved); // This is outside DB, should be after commitreturn saved;
}
privatevoidsendConfirmationEmail(Order order) {
// Simulating external call
}
}
⚠ Transaction Boundary Trap
Do not perform I/O (email, HTTP calls, file writes) inside a transaction. It extends the transaction duration, holds locks, and can cause database deadlocks under load.
📊 Production Insight
Long-running transactions are a silent killer. They hold database locks, fill the connection pool, and trigger serialisation failures.
Set explicit @Transactional(timeout = 30) to fail fast instead of accumulating blocking connections.
Monitor transaction duration via metrics — any transaction exceeding 500ms needs investigation.
🎯 Key Takeaway
Session is the unit of work; transaction is the unit of consistency.
Keep transactions short — never mix database writes with external I/O.
Always set a timeout — no transaction should run indefinitely.
thecodeforge.io
Hibernate Orm Basics
HQL, JPQL, and Criteria API
While CRUD can be done via session.persist() and session.get(), complex queries require Hibernate Query Language (HQL) or Criteria API. HQL (and its standardised sibling JPQL) is an object-oriented query language that works on entity names and field names, not table and column names. Hibernate translates HQL into native SQL of the target database.
The Criteria API is type-safe and allows dynamic query construction at runtime. It's ideal for filtering based on user-provided inputs without string concatenation. However, it's verbose and can generate suboptimal SQL if not tuned. In io.thecodeforge, we prefer HQL for static queries and Criteria for dynamic filtering.
A critical performance insight:SELECT e FROM Entity e fetches all columns. If you only need a few fields, use DTO projections — SELECT new io.thecodeforge.dto.Summary(e.id, e.name) FROM Entity e. This reduces network and memory pressure. Also, always use pagination — setFirstResult() and setMaxResults() — to avoid loading thousands of entities into memory.
Hibernate: select o.id, o.total_amount, o.created_at from forge_orders o where o.customer_id=? order by o.created_at desc limit ?
🔥Forge Tip:
Use JOIN FETCH to eagerly load associations in a single query. Example: FROM Order o JOIN FETCH o.items WHERE o.id = :id — this avoids N+1.
📊 Production Insight
The biggest performance killer in HQL is implicit joins. A WHERE o.customer.name = 'John' without explicit JOIN generates a cross join — catastrophic on large tables.
Always write explicit JOIN or JOIN FETCH for every association used in WHERE or SELECT.
Monitor Hibernate statistics via JMX to spot unexpected queries and cache misses.
🎯 Key Takeaway
HQL works on entities, not tables — but it generates SQL.
Use DTO projections to avoid fetching full entity graphs.
Explicit JOINs are mandatory for performance; implicit joins are a cross-join trap.
Understanding Hibernate Caching (First and Second Level)
Hibernate has two built-in cache levels: First-Level Cache (L1) and Second-Level Cache (L2).
L1 is session-scoped and enabled by default. Every get() and load() first checks the L1 cache. It prevents duplicate SQL in the same session but is cleared when the session closes. The biggest L1 trap is that it holds all loaded entities until the session is closed or clear() is called. Processing 100,000 records in one session without clearing will cause an OutOfMemoryError.
L2 is SessionFactory-scoped and must be explicitly configured (e.g., using Ehcache, Redis, or Hazelcast). It caches entities across sessions. Use it for read-heavy, rarely updated entities. The downside: stale data. If another process updates the database directly, the L2 cache becomes outdated unless you configure appropriate cache concurrency strategies (READ_WRITE, NONSTRICT_READ_WRITE, TRANSACTIONAL).
In io.thecodeforge, we use L2 caching only for reference data (e.g., product categories, country codes) with a short TTL and regular cache invalidation on updates.
Think of L1 as your desk drawer (private, temporary) and L2 as the office filing cabinet (shared, persistent but slower).
L1 is always on; you pay for it in heap memory.
L2 is optional; it requires a cache provider and careful invalidation rules.
Never use L2 for mutable entities with high contention rates — stale reads will corrupt business logic.
📊 Production Insight
L1 caching without clear() during batch operations is the #1 cause of Hibernate OOM in batch processing.
Always batch and flush: session.flush(); session.clear(); every 20-50 records to keep heap stable.
L2 caching looks great on paper but adds complexity — stale data detection requires version fields (@Version) and careful timeout tuning.
🎯 Key Takeaway
L1 cache is free but dangerous at scale — flush and clear during batch jobs.
L2 cache is powerful but choose immutable or low-update entities only.
Versioning and caches: always use @Version for optimistic locking alongside caching.
Why You Need Fetch Strategies Before Your Database Implodes
The N+1 query problem is the silent killer of production apps. You load 100 orders. Hibernate then fires 101 SQL queries — one for the order list, one for each customer. That's N+1. Eager loading is the hammer that pulls an entire object graph into memory, even the fields you don't need. Lazy loading defers child collection fetches until you explicitly access them. The rule: default to lazy for collections, eager for single-ended associations like @ManyToOne. Use JOIN FETCH in JPQL when you know you'll need the children. If you skip this decision, your REST endpoint will crawl under load. Profile with datasource-proxy or log SQL to catch N+1 before your SRE pages you.
FetchStrategyExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge
@EntitypublicclassOrder {
@IdprivateLong id;
// Default lazy — won't load line items until .getItems() called
@OneToMany(fetch = FetchType.LAZY)
privateList<LineItem> items;
// Default eager for single-ended — join on every query
@ManyToOne(fetch = FetchType.EAGER)
privateCustomer customer;
// Explicit join fetch to solve N+1// JPQL: SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :idpublicOrderfetchWithItems() { /* see query above */ }
}
Output
Hibernate: select ... from Order where id=?
Hibernate: select ... from LineItem where order_id=?
⚠ Production Trap:
Never trust @OneToMany(fetch = EAGER) on a collection. One eager collection per entity multiplies your joins exponentially. Your DBA will send a strongly worded email.
🎯 Key Takeaway
Default to lazy on collections, eager on single-ended. Add JOIN FETCH only when you're about to iterate.
Inheritance Mapping: Don't Let Your Schema Lie
Object-oriented inheritance doesn't map cleanly to relational tables. Hibernate gives you four strategies, and choosing wrong locks you into a schema that punishes performance. SINGLE_TABLE dumps all subclasses into one table with a discriminator column. Fast reads, but nullable columns everywhere and wasted space. JOINED puts each class in its own table with shared primary keys — normalized but five joins for a five-level hierarchy. TABLE_PER_CLASS creates independent tables per subclass — duplicates columns, breaks polymorphic queries, and violates uniqueness. The pragmatic pick: SINGLE_TABLE for shallow hierarchies under six subclasses. Use JOINED only when your data model enforces strict referential integrity. Avoid TABLE_PER_CLASS unless you really love debugging.
-- All data in one table, vehicle_type decides row shape.
🔥Why It Matters:
The chosen strategy dictates join complexity, index usage, and storage. A legacy system I inherited used JOINED on a 12-level hierarchy — that table had 50 joins on the main report query. Rewriting to SINGLE_TABLE cut query time from 8 seconds to 200ms.
🎯 Key Takeaway
Pick SINGLE_TABLE for flat hierarchies. JOINED only when normalized schema is non-negotiable. Never TABLE_PER_CLASS.
● Production incidentPOST-MORTEMseverity: high
The Silent N+1 Query Problem That Brought Down a Payment Service
Symptom
Payment processing endpoint timed out under moderate load. CPU and database connections spiked.
Assumption
Hibernate handles all queries efficiently with its default fetching strategies.
Root cause
An entity had a lazy-loaded collection. Each access triggered a separate SELECT statement, causing N+1 queries per request.
Fix
Changed the query to use JOIN FETCH or @EntityGraph(attributePaths = {'items'}) to eagerly load the collection in one SQL.
Key lesson
Never trust default lazy loading for hot-path reads.
Always enable Hibernate SQL logging in staging and profile it.
Use integration tests that assert the number of SQL statements generated.
Production debug guideSymptom → Action guide for the three most frequent production problems3 entries
Symptom · 01
LazyInitializationException when accessing a collection outside a transaction
→
Fix
Enable spring.jpa.open-in-view=false and ensure the association is fetched within the transaction. Prefer JOIN FETCH or DTO projection.
Symptom · 02
Response times degrade linearly as data grows — likely N+1 queries
→
Fix
Turn on SQL logging: logging.level.org.hibernate.SQL=DEBUG. Count SELECTs per request. Add hibernate.query.fail_on_pagination_over_collection_fetch=true to fail fast.
Symptom · 03
Connection pool exhausted with active sessions that never close
→
Fix
Check for unclosed Session/EntityManager in long-running operations. Use try-with-resources or Spring's @Transactional to guarantee cleanup. Verify pool max size and timeout settings.
★ Hibernate Quick Debug Cheat SheetFor the three most common runtime failures, run these commands and fixes immediately.
LazyInitializationException−
Immediate action
Add `spring.jpa.open-in-view=true` temporarily (not for production) to verify the data exists.
Add `spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true` as emergency override (bad for performance).
Fix now
Rewrite the query with JOIN FETCH or use @EntityGraph to preload the association.
N+1 queries (slow response under load)+
Immediate action
Add `-Dhibernate.query.fail_on_pagination_over_collection_fetch=true` to fail on known dangerous patterns.
Commands
`logging.level.org.hibernate.SQL=TRACE` to see every statement.
Use P6Spy or datasource-proxy to count SQL statements in a single HTTP request.
Fix now
Add JOIN FETCH to the HQL/JPQL or set @BatchSize on the collection.
Session leak causing connection pool exhaustion+
Immediate action
Check active connections: `SELECT count(*) FROM pg_stat_activity` or equivalent.
Commands
Add `spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false` to prevent unintended statements.
Monitor session stats via JMX bean `org.hibernate:type=Statistics, sessionFactory=*`.
Fix now
Wrap every Session usage in try-with-resources or ensure @Transactional closes the EntityManager.
Feature
Traditional JDBC
Hibernate ORM
SQL Writing
Manual (String-based, error-prone)
Automated (Generated at runtime)
Object Mapping
Manual (ResultSet.getXXX loop)
Automatic (Reflection-based)
Portability
Hardcoded SQL Dialects
Dialect Independent (HQL/JPQL)
Caching
None (Must build manually)
Built-in L1 and L2 Caching
Transaction Management
Verbose try-catch-finally
Declarative and Integrated
⚙ Quick Reference
6 commands from this guide
File
Command / Code
Purpose
iothecodeforgepersistenceOrderEntity.java
@Entity
Entity Mapping and JPA Annotations
iothecodeforgeserviceOrderService.java
@Service
Session Lifecycle and Transaction Management
iothecodeforgepersistenceOrderQueryService.java
@Service
HQL, JPQL, and Criteria API
srcmainresourcesehcache.xml
Understanding Hibernate Caching (First and Second Level)
FetchStrategyExample.java
@Entity
Why You Need Fetch Strategies Before Your Database Implodes
InheritanceMapping.java
@Entity
Inheritance Mapping
Key takeaways
1
Hibernate ORM acts as the bridge between Java's object-oriented model and the relational database schema.
2
The core unit of work is the 'Session', while the configuration is managed by the 'SessionFactory'.
3
Annotations like @Entity and @Table replace hundreds of lines of boilerplate JDBC code.
4
Mastering the object lifecycle (Transient, Persistent, Detached) is non-negotiable for production-grade development.
5
Always monitor generated SQL to ensure Hibernate isn't performing inefficient 'N+1' queries behind the scenes.
6
First-level cache is automatic but must be cleared during batch processing to avoid OOM.
Common mistakes to avoid
4 patterns
×
Not clearing the first-level cache when processing large datasets
Symptom
OutOfMemoryError after processing many records in a single session because all entities are retained in the L1 cache.
Fix
Periodically call session.flush() and session.clear() in loops (e.g., every 20-50 records). Use stateless sessions or JDBC batch for truly large operations.
×
Forgetting to close the Session (or EntityManager)
Symptom
Connection pool exhaustion after repeated requests; application becomes unresponsive.
Fix
Always use try-with-resources or Spring's @Transactional to guarantee session closure. Configure connection pool validation queries to detect stale connections.
×
Over-relying on @GeneratedValue(strategy = GenerationType.AUTO)
Symptom
Unexpected performance degradation: AUTO defaults to SEQUENCE or TABLE, which involve extra database round trips compared to IDENTITY.
Fix
Explicitly set strategy: use IDENTITY for MySQL (autoincrement), SEQUENCE for Oracle/PostgreSQL (with allocationSize > 1). Avoid TABLE if possible.
×
Passing managed entities directly to the view layer instead of using DTOs
Symptom
LazyInitializationException when the view accesses a lazy-loaded association after the session is closed.
Fix
Always convert entities to DTOs or use projections within the service/transaction. Keep the session closed by the time the view renders.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
What is the difference between a Transient, Persistent, and Detached obj...
Q02SENIOR
Explain the 'Impedance Mismatch' and how Hibernate solves it through met...
Q03SENIOR
How does Hibernate's 'Dirty Checking' mechanism work during a transactio...
Q04SENIOR
What is a SessionFactory, and why is it considered a thread-safe, heavyw...
Q05SENIOR
Can you explain the role of a Dialect in Hibernate and why it's necessar...
Q01 of 05SENIOR
What is the difference between a Transient, Persistent, and Detached object in the Hibernate lifecycle?
ANSWER
A Transient object is created with 'new' and not associated with any Session. A Persistent object is associated with a Session and reflecting changes in the database upon flush/commit. A Detached object was previously Persistent but its Session was closed — changes are not tracked until it's reattached via merge().
Q02 of 05SENIOR
Explain the 'Impedance Mismatch' and how Hibernate solves it through metadata mapping.
ANSWER
Impedance Mismatch refers to the fundamental difference between object-oriented (objects, inheritance, associations) and relational models (tables, foreign keys, normalization). Hibernate bridges this using metadata (annotations or XML) that define how object graphs map to table structures, how inheritance is stored (single table, joined, table-per-class), and how relationships are represented via foreign keys and join tables.
Q03 of 05SENIOR
How does Hibernate's 'Dirty Checking' mechanism work during a transaction commit?
ANSWER
At flush time, Hibernate compares each managed entity's current state with a snapshot taken when the entity was loaded. If any persistent field differs, it generates an UPDATE statement for only the changed columns. This is efficient but can be expensive if you load many entities — that's why read-only operations should use read-only transactions.
Q04 of 05SENIOR
What is a SessionFactory, and why is it considered a thread-safe, heavyweight object compared to a Session?
ANSWER
SessionFactory is a thread-safe singleton that holds parsed ORM metadata, connection pool configuration, second-level cache, and SQL generation logic. It is expensive to build (reads configuration, validates mappings) so it's created once per database. Session is lightweight, single-threaded, and represents a unit of work; it should be opened per request and closed after use.
Q05 of 05SENIOR
Can you explain the role of a Dialect in Hibernate and why it's necessary for database portability?
ANSWER
The Dialect abstracts database-specific SQL features: pagination syntax (LIMIT vs ROWNUM), sequence generation, data types, and locking clauses. When Hibernate generates SQL for queries or DDL, it consults the Dialect. This allows the same Java code and HQL to run on different databases by simply changing the Dialect configuration.
01
What is the difference between a Transient, Persistent, and Detached object in the Hibernate lifecycle?
SENIOR
02
Explain the 'Impedance Mismatch' and how Hibernate solves it through metadata mapping.
SENIOR
03
How does Hibernate's 'Dirty Checking' mechanism work during a transaction commit?
SENIOR
04
What is a SessionFactory, and why is it considered a thread-safe, heavyweight object compared to a Session?
SENIOR
05
Can you explain the role of a Dialect in Hibernate and why it's necessary for database portability?
SENIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
Is Hibernate the same as JPA?
No. JPA (Jakarta Persistence API) is a specification—a set of rules and interfaces. Hibernate is the most popular 'implementation' of that specification. Think of JPA as the blueprint and Hibernate as the actual building.
Was this helpful?
02
Does Hibernate slow down my application?
While there is a slight overhead due to reflection and SQL generation, Hibernate's built-in caching and batch fetching often make it faster than poorly written manual JDBC.
Was this helpful?
03
Do I still need to know SQL if I use Hibernate?
Absolutely. You must understand what's happening under the hood to debug performance issues and write optimized HQL (Hibernate Query Language) for complex reports.
Was this helpful?
04
What is an 'Impedance Mismatch'?
It refers to the difficulty of mapping objects (which have inheritance and associations) to tables (which use foreign keys and flat structures). Hibernate automates this translation.
Was this helpful?
05
What is the difference between `get()` and `load()` in Hibernate?
session.get() returns null if the entity doesn't exist and hits the database immediately. session.load() returns a proxy and only throws an exception if the proxy is accessed and the row doesn't exist — it may not hit the database until property access.
Was this helpful?
06
Should I use Field or Property access in JPA?
Use field access (annotations on fields) unless you need custom getter/setter logic. Property access (annotations on getters) can lead to bugs when getters have side effects or computed values. Stick to field access for consistency.