Spring Data JPA విడమరిచి: Tables నుండి Java Objects వరకు
ORM concepts, entity mappings, relationships, N+1 సమస్య మరియు solutions, derived queries, JPQL, native SQL, transaction isolation మరియు propagation, Flyway migrations, మరియు database indexing strategies కవర్ చేసే Spring Data JPA కి సమగ్ర మార్గదర్శి.
JPA ఎందుకు ఉంది: అనువాద సమస్య
మీరు ఇంగ్లీష్ మాట్లాడతారు, మీ స్నేహితుడు స్పానిష్ మాట్లాడతాడు అని ఊహించుకోండి. మీరు ఒకరినొకరు అర్థం చేసుకోవడానికి మధ్యలో ఒక అనువాదకుడు కావాలి. JPA ఖచ్చితంగా అదే చేస్తుంది — ఇది మీ Java objects మరియు మీ database tables మధ్య అనువదిస్తుంది.
మీ database rows మరియు columns లో ఆలోచిస్తుంది. మీ Java code objects మరియు fields లో ఆలోచిస్తుంది. JPA లేకుండా, మీరు raw SQL రాయాలి, result sets నుండి data ను manually తీయాలి, మరియు ప్రతి query కోసం దాన్ని objects లోకి చేర్చాలి. JPA ఆ మొత్తం అనువాదాన్ని automate చేస్తుంది.
ORM అంటే ఏమిటి? (10 ఏళ్ల పిల్లాడికి చెప్పినట్లు)
ఒక library ని ఊహించుకోండి. Library లో cards తో ఒక catalog system ఉంది (అది మీ database). ప్రతి card లో సమాచారం ఉంది: పుస్తకం పేరు, రచయిత, shelf నంబర్. ఇప్పుడు మీరు మీ treehouse లో పుస్తకాలతో పని చేయాలని ఊహించుకోండి. మీకు catalog cards వద్దు — మీరు పట్టుకొని, చదవగలిగే, ఇచ్చి పుచ్చుకోగలిగే నిజమైన book objects కావాలి.
ORM (Object-Relational Mapping) అనేది catalog cards తీసుకొని మీ కోసం నిజమైన book objects గా మార్చే మాయాజాల librarian. మీరు ఒక book object ను మార్చినప్పుడు (ఉదాహరణకు, దానిలో ఒక note రాస్తే), librarian వెళ్ళి catalog card ను కూడా update చేస్తుంది.
- JPA — specification (మాయాజాల librarian ఎలా పని చేయాలో rulebook)
- Hibernate — implementation (ఆ rules ను follow చేసే నిజమైన librarian)
- Spring Data JPA — convenience layer (మీరు librarian కి detailed instructions బదులు plain English లో చెప్తారు)
@Entity: ఒక Table ను Java Class గా మార్చడం
@Entity annotation JPA కి చెప్తుంది: "ఈ Java class ఒక database table కు map అవుతుంది." Class యొక్క ప్రతి instance ఒక row ని represent చేస్తుంది. ప్రతి field ఒక column ని represent చేస్తుంది.
@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String title;
@Column(name = "page_count")
private int pageCount;
@Column(nullable = false)
private String isbn;
@Column(name = "published_date")
private LocalDate publishedDate;
// Getters and setters omitted for brevity
}
Annotations ను విడమరుద్దాం:
@Id— primary key ని mark చేస్తుంది. ప్రతి entity కి ఒకటి ఉండాలి.@GeneratedValue(strategy = GenerationType.IDENTITY)— database ID ని auto-generate చేస్తుంది (auto-increment column లాగా).@Column— column ను fine-tune చేస్తుంది: name, nullability, length. దీన్ని skip చేస్తే, JPA field name ను column name గా ఉపయోగిస్తుంది.@Table(name = "books")— table name ను explicitly set చేస్తుంది. ఇది లేకపోతే, JPA class name ను ఉపయోగిస్తుంది.
Relationships: Tables ఎలా కనెక్ట్ అవుతాయి
నిజ జీవితంలో, విషయాలు కనెక్ట్ అయి ఉంటాయి. ఒక రచయిత చాలా పుస్తకాలు రాస్తారు. ఒక విద్యార్థి చాలా courses లో enroll అవుతారు. ఒక order చాలా items కలిగి ఉంటుంది. JPA ఈ connections ను మీ Java classes లో నేరుగా express చేయనిస్తుంది.
@OneToMany మరియు @ManyToOne — Parent-Child Connection
ఒక classroom ని ఊహించుకోండి. ఒక teacher కి చాలా students ఉంటారు. Teacher వైపు నుండి, ఇది "one to many." ప్రతి student వైపు నుండి, ఇది "many to one" — చాలా students ఒక teacher ని share చేసుకుంటారు.
@Entity
@Table(name = "authors")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books = new ArrayList<>();
// రెండు sides ను sync లో ఉంచడానికి helper method
public void addBook(Book book) {
books.add(book);
book.setAuthor(this);
}
}
@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id", nullable = false)
private Author author;
}
ముఖ్యమైన అంశాలు:
mappedBy = "author"—Bookentity relationship ని own చేస్తుందని JPA కి చెప్తుంది (దాని దగ్గరauthor_idforeign key column ఉంది).cascade = CascadeType.ALL— మీరు author ను save/delete చేసినప్పుడు, వారి books అన్నీ కూడా save/delete అవుతాయి.orphanRemoval = true— మీరు author list నుండి ఒక book ని remove చేస్తే, JPA దాన్ని database నుండి delete చేస్తుంది.fetch = FetchType.LAZY— book load చేసేటప్పుడు author ను load చేయకు, మీరు actually access చేసేవరకు. ఇది performance కి critical.
@ManyToMany — Network Connection
Students మరియు courses ఒక perfect ఉదాహరణ. ఒక student చాలా courses తీసుకుంటారు. ఒక course లో చాలా students ఉంటారు. ఏదీ మరొకదాన్ని "own" చేయదు — వారు ఒక join table ను share చేసుకుంటారు.
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "student_courses",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
@JoinTable bridge table ను define చేస్తుంది. JPA రెండు foreign key columns తో student_courses ను create చేస్తుంది. Hibernate bag semantics తో duplicate-row bug నివారించడానికి ManyToMany కోసం List బదులు Set ఉపయోగించండి.
N+1 సమస్య: వెయ్యి Queries ద్వారా మరణం
ఇది JPA లో అత్యంత సాధారణ performance trap. Librarian analogy ని మళ్ళీ ఉపయోగిద్దాం.
మీరు librarian ని అడిగారు: "నాకు 50 మంది authors ఇవ్వు." Librarian మీకు 50 author cards తెస్తుంది. తర్వాత మీరు అంటారు: "ఇప్పుడు ప్రతి author యొక్క books చూపించు." Librarian shelf కి 50 సార్లు ప్రత్యేకంగా నడుస్తుంది — ప్రతి author కి ఒకసారి. అది authors కోసం 1 query + books కోసం 50 queries = 51 queries. అదే N+1 సమస్య.
తెలివైన approach? Librarian కి ఒక list ఇచ్చి చెప్పండి: "నాకు అన్ని authors మరియు వారి books ను ఒకే trip లో తీసుకురా." దిగువ solutions అదే చేస్తాయి.
దాన్ని ఎలా గుర్తించాలి
application.yml లో SQL logging enable చేయండి:
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
వేర్వేరు IDs తో అదే SELECT repeat అవుతూ కనిపిస్తే, మీకు N+1 సమస్య ఉంది.
Solution 1: JOIN FETCH (JPQL)
JOIN ఉపయోగించి ఒకే query లో అంతా load చేయమని JPA కి చెప్పండి.
public interface AuthorRepository extends JpaRepository<Author, Long> {
@Query("SELECT a FROM Author a JOIN FETCH a.books WHERE a.id = :id")
Optional<Author> findByIdWithBooks(@Param("id") Long id);
@Query("SELECT DISTINCT a FROM Author a JOIN FETCH a.books")
List<Author> findAllWithBooks();
}
JOIN FETCH Hibernate కి చెప్తుంది: "authors load చేసేటప్పుడు, వారి books ను కూడా అదే SQL query లో load చేయి." ఒక author కి multiple books ఉన్నప్పుడు duplicate authors ను నివారించడానికి DISTINCT keyword ఉపయోగిస్తారు.
Solution 2: @EntityGraph (Declarative)
మీరు JPQL రాయకూడదనుకుంటే, ఏమి eagerly fetch చేయాలో declaratively specify చేయడానికి @EntityGraph ఉపయోగించండి.
public interface AuthorRepository extends JpaRepository<Author, Long> {
@EntityGraph(attributePaths = {"books"})
List<Author> findAll();
@EntityGraph(attributePaths = {"books"})
Optional<Author> findById(Long id);
}
@EntityGraph ఆ specific query కోసం default LAZY fetch ని override చేస్తుంది. ఇది behind the scenes ఒక LEFT JOIN generate చేస్తుంది. JOIN FETCH కంటే advantage ఏమిటంటే మీరు JPQL రాయవలసిన అవసరం లేదు.
Solution 3: DTO Projections (Read-Only కోసం ఉత్తమం)
కొన్నిసార్లు మీకు full entity అవసరం లేదు — specific fields మాత్రమే. DTO projections మీకు కావలసినది మాత్రమే fetch చేస్తాయి, managed entities overhead ను పూర్తిగా skip చేస్తాయి.
// DTO class
public record AuthorSummary(String name, long bookCount) {}
// Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {
@Query("SELECT new com.example.dto.AuthorSummary(a.name, COUNT(b)) " +
"FROM Author a LEFT JOIN a.books b GROUP BY a.name")
List<AuthorSummary> findAuthorSummaries();
}
DTO projections fastest option ఎందుకంటే:
- అవి Hibernate entity tracking (persistence context) ను skip చేస్తాయి.
- అవి మీరు specify చేసిన columns మాత్రమే fetch చేస్తాయి.
- Data చూపించే కానీ edit చేయని dashboards, reports, మరియు list views కోసం perfect.
Derived Queries: Spring మీ Method Name చదువుతుంది
Spring Data JPA మీ method name నుండి SQL generate చేయగలదు. మీరు method signature రాస్తారు, Spring query రాస్తుంది. ఇది magic లాగా అనిపిస్తుంది.
public interface BookRepository extends JpaRepository<Book, Long> {
// SELECT * FROM books WHERE title = ?
List<Book> findByTitle(String title);
// SELECT * FROM books WHERE page_count > ?
List<Book> findByPageCountGreaterThan(int pages);
// SELECT * FROM books WHERE title LIKE '%keyword%' AND page_count < ?
List<Book> findByTitleContainingAndPageCountLessThan(String keyword, int maxPages);
// SELECT * FROM books WHERE author_id = ? ORDER BY published_date DESC
List<Book> findByAuthorIdOrderByPublishedDateDesc(Long authorId);
// SELECT COUNT(*) FROM books WHERE author_id = ?
long countByAuthorId(Long authorId);
// SELECT * FROM books WHERE isbn = ?
Optional<Book> findByIsbn(String isbn);
// DELETE FROM books WHERE page_count < ?
void deleteByPageCountLessThan(int pages);
}
Spring method name ను parts గా parse చేస్తుంది: findBy + Title + Containing + And + PageCount + LessThan. ప్రతి part ఒక SQL clause కి map అవుతుంది. Supported keywords: And, Or, Between, LessThan, GreaterThan, Like, Containing, In, OrderBy, Not, IsNull, మరియు మరిన్ని.
@Query: Method Names చాలా పొడవుగా అయినప్పుడు
Derived queries simple lookups కోసం గొప్పవి. కానీ queries complex అయినప్పుడు, method names చదవలేనంతగా అవుతాయి. అప్పుడు @Query ఉపయోగిస్తారు.
JPQL (Java Persistence Query Language)
JPQL SQL లాగా కనిపిస్తుంది కానీ tables మరియు columns బదులు entities మరియు fields మీద పని చేస్తుంది.
public interface BookRepository extends JpaRepository<Book, Long> {
// JPQL — entity names ఉపయోగిస్తుంది, table names కాదు
@Query("SELECT b FROM Book b WHERE b.author.name = :authorName AND b.pageCount > :minPages")
List<Book> findByAuthorAndMinPages(
@Param("authorName") String authorName,
@Param("minPages") int minPages
);
// Update query
@Modifying
@Query("UPDATE Book b SET b.title = :newTitle WHERE b.id = :bookId")
int updateTitle(@Param("bookId") Long bookId, @Param("newTitle") String newTitle);
}
Native SQL
మీకు database-specific features (window functions, CTEs, specific index hints) అవసరమైనప్పుడు, native queries ఉపయోగించండి.
public interface BookRepository extends JpaRepository<Book, Long> {
// Native SQL — actual table/column names ఉపయోగిస్తుంది
@Query(value = "SELECT b.* FROM books b " +
"JOIN authors a ON b.author_id = a.id " +
"WHERE a.name = :authorName " +
"ORDER BY b.published_date DESC LIMIT 5",
nativeQuery = true)
List<Book> findLatestByAuthor(@Param("authorName") String authorName);
// Window function — JPQL లో సాధ్యం కాదు
@Query(value = "SELECT b.title, b.page_count, " +
"RANK() OVER (ORDER BY b.page_count DESC) as rank " +
"FROM books b",
nativeQuery = true)
List<Object[]> findBooksRankedByLength();
}
ఏది ఎప్పుడు ఉపయోగించాలి:
- Derived queries — 1-2 conditions తో simple lookups
- JPQL — మధ్యస్థ complexity, database independence కావాలి
- Native SQL — database-specific features, complex analytics, performance-critical queries
Transactions: అంతా లేదా ఏమీ లేదు
Transaction అంటే trip కోసం suitcase pack చేయడం లాంటిది. అంతా లోపలికి వెళ్తుంది (commit), లేదా అంతా unpack చేసి మళ్ళీ మొదలుపెడతారు (rollback). మీరు airport కి సగం బట్టలతో చేరుకోవడం ఎప్పుడూ కోరుకోరు.
Banking transfer లో: account A నుండి తీసివేయండి మరియు account B కి జోడించండి. రెండవ step fail అయితే, మొదటిది undo కావాలి. Transactions ఇది guarantee చేస్తాయి.
Transaction Isolation Levels
Isolation levels transactions ఒకదానికొకటి uncommitted work ను ఎంత "చూడగలవో" నియంత్రిస్తాయి. Exam students మధ్య walls లాగా ఆలోచించండి:
- READ_UNCOMMITTED — walls లేవు. మీ neighbor యొక్క unfinished answers చూడవచ్చు (dirty reads). దాదాపు ఎప్పుడూ ఉపయోగించరు.
- READ_COMMITTED — సన్నని walls. మీ neighbor submit చేసిన (committed) answers మాత్రమే చూడగలరు. PostgreSQL కోసం ఇది default.
- REPEATABLE_READ — దళసరి walls. మీరు ఒక value చదివిన తర్వాత, మీ neighbor update చేసినా, మీ transaction సమయంలో ఇది మారదు.
- SERIALIZABLE — soundproof rooms. Transactions ఒకదాని తర్వాత ఒకటి run అవుతున్నట్లు execute అవుతాయి. అత్యంత safe కానీ అత్యంత slow.
@Service
public class TransferService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId)
.orElseThrow(() -> new AccountNotFoundException(fromId));
Account to = accountRepository.findById(toId)
.orElseThrow(() -> new AccountNotFoundException(toId));
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException(fromId, amount);
}
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
accountRepository.save(from);
accountRepository.save(to);
// ఏ line throw చేసినా, EVERYTHING rolls back
}
}
Transaction Propagation
Propagation ఒక transactional method మరొక transactional method ను call చేసినప్పుడు ఏమి జరుగుతుందో నియంత్రిస్తుంది. Russian nesting dolls లాగా ఆలోచించండి:
// REQUIRED (default) — ఇప్పటికే ఉన్న transaction లో join అవు లేదా కొత్తది create చేయి
@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder(Order order) {
orderRepository.save(order);
paymentService.processPayment(order); // ఈ transaction లో join అవుతుంది
// Payment fail అయితే, order save కూడా roll back అవుతుంది
}
// REQUIRES_NEW — ఎప్పుడూ కొత్త transaction create చేయి
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAuditEvent(String event) {
auditRepository.save(new AuditLog(event));
// Calling transaction roll back అయినా,
// ఈ audit log save అవుతుంది — దీనికి సొంత transaction ఉంది
}
// SUPPORTS — transaction ఉంటే ఉపయోగించు, లేకపోతే లేకుండా run అవు
@Transactional(propagation = Propagation.SUPPORTS)
public Book findBook(Long id) {
return bookRepository.findById(id).orElse(null);
}
// MANDATORY — ఇప్పటికే ఉన్న transaction లో call చేయాలి
@Transactional(propagation = Propagation.MANDATORY)
public void deductInventory(Long productId, int quantity) {
// Transaction లేకపోతే exception throw చేస్తుంది
// Callers ను వారి సొంత transaction లో wrap చేయమని force చేస్తుంది
}
// NEVER — transaction లో call చేయకూడదు
@Transactional(propagation = Propagation.NEVER)
public Report generateReport() {
// Read-heavy operations కోసం transaction overhead లేదని guarantee
return reportRepository.buildReport();
}
సాధారణ తప్పు: అదే class లో @Transactional method ను call చేయడం కొత్త transaction create చేయదు. Spring proxy class బయట నుండి వచ్చే calls ను మాత్రమే intercept చేస్తుంది. Internal గా transactional method call చేయాలంటే, class ను itself లోకి inject చేయండి లేదా method ను separate service లోకి extract చేయండి.
Flyway Migrations: మీ Database కోసం Version Control
మీ code Git లో ఉంది. మీ database schema కూడా అక్కడే ఉండాలి. Flyway ఏ SQL scripts apply అయ్యాయో track చేస్తుంది మరియు startup లో కొత్తవి automatically run చేస్తుంది.
ఇది checklist లాగా ఆలోచించండి. Flyway ప్రతి script run చేసేటప్పుడు mark చేస్తుంది. మీరు రెండు కొత్త scripts తో కొత్త version deploy చేస్తే, Flyway ఆ రెండు మాత్రమే run చేస్తుంది — పాతవి ఎప్పటికీ re-run చేయదు.
Naming Convention
src/main/resources/db/migration/
V1__create_books_table.sql
V2__create_authors_table.sql
V3__add_author_id_to_books.sql
V4__create_student_courses_table.sql
V5__add_isbn_index.sql
Naming format: V{number}__{description}.sql (version number తర్వాత రెండు underscores). Flyway వాటిని version order లో run చేస్తుంది.
ఉదాహరణ Migrations
-- V1__create_books_table.sql
CREATE TABLE books (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
isbn VARCHAR(13) NOT NULL UNIQUE,
page_count INT NOT NULL DEFAULT 0,
published_date DATE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- V2__create_authors_table.sql
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- V3__add_author_id_to_books.sql
ALTER TABLE books ADD COLUMN author_id BIGINT;
ALTER TABLE books ADD CONSTRAINT fk_books_author
FOREIGN KEY (author_id) REFERENCES authors(id);
-- V4__create_student_courses_table.sql
CREATE TABLE students (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE courses (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL
);
CREATE TABLE student_courses (
student_id BIGINT NOT NULL REFERENCES students(id),
course_id BIGINT NOT NULL REFERENCES courses(id),
enrolled_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (student_id, course_id)
);
Golden rules:
- ఇప్పటికే apply అయిన migration ను ఎప్పటికీ edit చేయకండి. మార్పులు చేయడానికి కొత్త migration create చేయండి.
- Deploy చేయడానికి ముందు production data copy మీద migrations ను ఎల్లప్పుడూ test చేయండి.
- Versioned migrations (ఒకసారి run) కోసం
Vమరియు repeatable migrations (మారినప్పుడు re-run, views మరియు stored procedures కోసం ఉపయోగకరం) కోసంRఉపయోగించండి.
Database Indexing: Queries ను వేగంగా చేయడం
Index అంటే textbook వెనుక ఉన్న index లాంటిది. అది లేకుండా, "photosynthesis" కనుగొనడానికి ప్రతి page తిరగేయాలి. అది ఉంటే, index లో "photosynthesis" చూసి నేరుగా page 142 కి వెళ్తారు.
Databases అదే విధంగా పని చేస్తాయి. Index లేకుండా, database ప్రతి row scan చేస్తుంది ("full table scan"). సరైన index ఉంటే, matching rows కి నేరుగా jump చేస్తుంది.
B-Tree Index (Default)
B-tree default index type. Equality checks (=), range queries (<, >, BETWEEN), మరియు sorting (ORDER BY) కోసం బాగా పని చేస్తుంది.
-- Single column index
CREATE INDEX idx_books_isbn ON books(isbn);
-- JPA entity లో ఉపయోగం
@Entity
@Table(name = "books", indexes = {
@Index(name = "idx_books_isbn", columnList = "isbn"),
@Index(name = "idx_books_title", columnList = "title")
})
public class Book {
// ...
}
Composite Index (Multiple Columns)
మీరు తరచుగా multiple columns కలిపి query చేసినప్పుడు, composite index separate single-column indexes కంటే చాలా efficient.
-- Composite index — column order ముఖ్యం!
CREATE INDEX idx_books_author_date ON books(author_id, published_date);
-- ఈ index ఈ queries కి సహాయపడుతుంది:
-- WHERE author_id = 5 (index ఉపయోగిస్తుంది)
-- WHERE author_id = 5 AND published_date > '2024-01-01' (index ఉపయోగిస్తుంది)
-- WHERE published_date > '2024-01-01' (index ఉపయోగించదు!)
-- Rule: index leftmost prefix rule ను follow చేస్తుంది.
-- Phone book last name, తర్వాత first name ద్వారా sort అయినట్లు ఆలోచించండి.
-- "Smith" ను త్వరగా చూడవచ్చు (last name ముందు).
-- "Smith, John" ను త్వరగా చూడవచ్చు (రెండు columns).
-- కేవలం "John" ను త్వరగా చూడలేరు (మొదటి column skip చేస్తే).
Partial Index (Conditional Index)
మీరు ఎప్పుడూ query చేయని rows ను ఎందుకు index చేయాలి? Partial index ఒక condition కి match అయ్యే rows ను మాత్రమే cover చేస్తుంది, చిన్నగా మరియు వేగంగా చేస్తుంది.
-- Published books ను మాత్రమే index చేయి (drafts skip చేయి)
CREATE INDEX idx_books_published ON books(title)
WHERE status = 'PUBLISHED';
-- Active users ను మాత్రమే index చేయి
CREATE INDEX idx_users_active_email ON users(email)
WHERE is_active = true;
-- Partial indexes చిన్నవి, update చేయడానికి వేగం, తక్కువ disk space వాడతాయి.
-- మీరు ఎల్లప్పుడూ specific condition ద్వారా filter చేసినప్పుడు perfect.
GIN Index (Full-Text Search)
GIN (Generalized Inverted Index) text, arrays, మరియు JSON లో search చేయడానికి design చేయబడింది. ఇది PostgreSQL full-text search యొక్క backbone.
-- Full-text search index
CREATE INDEX idx_books_search ON books
USING GIN (to_tsvector('english', title || ' ' || description));
-- Index ఉపయోగించి query
SELECT * FROM books
WHERE to_tsvector('english', title || ' ' || description)
@@ to_tsquery('english', 'java & programming');
-- JSON index (JSONB columns కోసం)
CREATE INDEX idx_books_metadata ON books USING GIN (metadata);
-- Index support తో JSON query
SELECT * FROM books WHERE metadata @> '{"genre": "fiction"}'::jsonb;
Indexing Best Practices
- WHERE, JOIN, మరియు ORDER BY clauses లో ఉన్న columns ను index చేయండి — database search మరియు sort చేసే columns అవి.
- Over-index చేయకండి — ప్రతి index INSERT, UPDATE, మరియు DELETE ను slow చేస్తుంది ఎందుకంటే index కూడా update కావాలి.
- EXPLAIN ANALYZE ఉపయోగించండి — మీ query నిజంగా index ఉపయోగిస్తుందో ఎల్లప్పుడూ check చేయండి. Table చిన్నది అయితే database దాన్ని ignore చేయవచ్చు.
- Index usage monitor చేయండి — ఎవరూ ఉపయోగించని indexes ను drop చేయండి. అవి disk waste చేస్తాయి మరియు writes ను slow చేస్తాయి.
-- మీ query index ఉపయోగిస్తుందా check చేయండి
EXPLAIN ANALYZE SELECT * FROM books WHERE isbn = '978-0134685991';
-- PostgreSQL లో ఉపయోగించని indexes కనుగొనండి
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
అన్నీ కలిపి చూద్దాం
Relationships తో ఒక entity, mixed query types తో ఒక repository, మరియు ఒక transactional service method చూపించే complete ఉదాహరణ:
// Relationship మరియు index తో Entity
@Entity
@Table(name = "orders", indexes = {
@Index(name = "idx_orders_customer", columnList = "customer_id"),
@Index(name = "idx_orders_date", columnList = "orderDate")
})
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
private LocalDateTime orderDate;
private BigDecimal totalAmount;
}
// Derived + JPQL + native queries తో Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Derived query
List<Order> findByCustomerIdOrderByOrderDateDesc(Long customerId);
// JPQL with JOIN FETCH (N+1 ను solve చేస్తుంది)
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items WHERE o.customer.id = :customerId")
List<Order> findByCustomerWithItems(@Param("customerId") Long customerId);
// DTO projection
@Query("SELECT new com.example.dto.OrderSummary(o.id, o.totalAmount, o.orderDate) " +
"FROM Order o WHERE o.customer.id = :customerId")
List<OrderSummary> findOrderSummaries(@Param("customerId") Long customerId);
}
// Transactional service
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
@Transactional
public Order placeOrder(Long customerId, List<OrderItemRequest> items) {
Order order = new Order();
order.setCustomer(customerRepository.getReferenceById(customerId));
order.setOrderDate(LocalDateTime.now());
BigDecimal total = BigDecimal.ZERO;
for (OrderItemRequest item : items) {
inventoryService.deductStock(item.productId(), item.quantity());
OrderItem orderItem = new OrderItem(item.productId(), item.quantity(), item.price());
order.getItems().add(orderItem);
orderItem.setOrder(order);
total = total.add(item.price().multiply(BigDecimal.valueOf(item.quantity())));
}
order.setTotalAmount(total);
return orderRepository.save(order);
// deductStock లేదా save throw చేస్తే, EVERYTHING rolls back
}
}
తరచుగా అడిగే ప్రశ్నలు
1. Production లో Hibernate auto DDL generation (hbm2ddl.auto) ఉపయోగించాలా?
ఎప్పటికీ వద్దు. Hibernate auto DDL (spring.jpa.hibernate.ddl-auto=update) quick prototyping కోసం fine, కానీ columns rename చేయడం, data migrate చేయడం, లేదా partial indexes add చేయడం వంటి complex schema changes handle చేయలేదు. Production లో, ఎల్లప్పుడూ Flyway లేదా Liquibase వంటి migration tool ఉపయోగించండి. ఈ tools version-controlled, repeatable, మరియు auditable schema changes ఇస్తాయి. ఇలా ఆలోచించండి: మీరు Git లేకుండా code deploy చేయరు, కాబట్టి migration tool లేకుండా schema changes deploy చేయకండి.
2. FetchType.EAGER vs FetchType.LAZY ఎప్పుడు ఉపయోగించాలి?
దాదాపు ఎల్లప్పుడూ LAZY ఉపయోగించండి. Eager fetching మీరు entity load చేసిన ప్రతిసారి related data ను load చేస్తుంది, మీకు అవసరం లేకపోయినా. Author కి 500 books ఉంటే మీకు author name మాత్రమే కావాలంటే, EAGER loading 500 books ను ఎందుకూ లేకుండా fetch చేస్తుంది. Default గా LAZY ఉపయోగించండి మరియు అవసరమైనప్పుడు JOIN FETCH, @EntityGraph, లేదా DTO projections ఉపయోగించి selectively relationships load చేయండి. ఏకైక exception @ManyToOne మరియు @OneToOne, related entity చిన్నది మరియు దాదాపు ఎల్లప్పుడూ అవసరమైనప్పుడు.
3. JpaRepository.save() మరియు saveAndFlush() మధ్య తేడా ఏమిటి?
save() entity ను "persist చేయవలసిన" అని mark చేస్తుంది కానీ SQL ను database కి ఎప్పుడు పంపాలో Hibernate decide చేస్తుంది (సాధారణంగా transaction commit వద్ద). saveAndFlush() SQL ను వెంటనే పంపమని force చేస్తుంది. Database-generated ID వెంటనే కావాలంటే (ఉదాహరణకు, మరొక service కి pass చేయడానికి) లేదా transaction ముగిసే ముందు database constraint violation catch చేయాలంటే saveAndFlush() ఉపయోగించండి. చాలా సందర్భాలలో, save() సరిపోతుంది మరియు ఎక్కువ efficient ఎందుకంటే Hibernate multiple saves ను తక్కువ SQL statements గా batch చేయగలదు.
4. JPQL మరియు native SQL queries మధ్య ఎలా ఎంచుకోవాలి?
మీ query వేర్వేరు databases లో పని చేయాలి మరియు database-specific features అవసరం లేనప్పుడు JPQL ఉపయోగించండి. JPQL entity names మరియు fields మీద operate చేస్తుంది, కాబట్టి ఇది database-agnostic. Window functions (RANK, ROW_NUMBER), common table expressions (CTEs), database-specific hints వంటి features అవసరమైనప్పుడు, లేదా maximum performance కావాలి మరియు generated SQL మీద full control కావాలంటే native SQL ఉపయోగించండి. మంచి rule: derived queries తో start చేయండి, method names పొడవుగా అయినప్పుడు JPQL కి graduate అవండి, JPQL express చేయలేనప్పుడు మాత్రమే native SQL ఉపయోగించండి.
5. ఒక table మీద ఎన్ని indexes create చేయాలి?
Fixed rule లేదు, కానీ ప్రతి index కి ఒక cost ఉంది. ప్రతి index reads ను speed up చేస్తుంది కానీ writes ను slow చేస్తుంది ఎందుకంటే ప్రతి INSERT, UPDATE, మరియు DELETE లో database index ను కూడా update చేయాలి. సాధారణ guideline: మీ అత్యంత frequent queries లో WHERE clauses, JOIN conditions, మరియు ORDER BY clauses లో కనిపించే columns ను index చేయండి. మీ queries నిజంగా indexes ఉపయోగిస్తున్నాయో verify చేయడానికి EXPLAIN ANALYZE ఉపయోగించండి. pg_stat_user_indexes తో index usage monitor చేయండి మరియు zero scans ఉన్న indexes drop చేయండి. సాధారణ table కోసం, 3-5 బాగా ఎంచుకున్న indexes సాధారణంగా సరిపోతాయి. Over-indexing under-indexing అంతే చెడ్డది.