Spring Data JPA chuqurroq

01.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 22 daqiqa o'qish

Bu mavzuda muhim qism: real database bilan ishlash, query yozish, pagination, sorting va entity relationlarni tushunish. Bu bo‘lim ichida quyidagilar bor: Derived query methods, @Query, Pagination & Sorting, One-to-One, One-to-Many, Many-to-Many, CascadeType, FetchType, @ManyToOne join columns.


1. Spring Data JPA nima?

Spring Data JPA - database bilan ishlashni osonlashtiradigan Spring moduli.

Oddiy qilib:

Java object  →  JPA/Hibernate  →  SQL  →  Database

Siz ko‘p hollarda SQL yozmaysiz. Spring Data JPA siz uchun query yaratadi.

Masalan:

public interface UserRepository extends JpaRepository<User, Long> {
}

Shu bitta interface orqali sizda avtomatik CRUD methodlar paydo bo‘ladi:

save()
findById()
findAll()
deleteById()
existsById()
count()

2. Entity va Repository eslatma

Entity

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String fullName;

    private String email;

    private Integer age;

    // getters/setters
}

Bu users jadvaliga mos class.


Repository

public interface UserRepository extends JpaRepository<User, Long> {
}

Bu yerda:

JpaRepository<User, Long>

ma’nosi:

User - qaysi entity bilan ishlaydi
Long - entity id tipi

3. Built-in CRUD methodlar

Spring Data JPA sizga tayyor methodlar beradi.

Saqlash

User user = new User();
user.setFullName("Ali Valiyev");
user.setEmail("ali@mail.com");
user.setAge(22);

userRepository.save(user);

Agar id == null bo‘lsa - yangi record yaratadi.

Agar id mavjud bo‘lsa - update qiladi.


ID orqali topish

User user = userRepository.findById(1L)
        .orElseThrow(() -> new RuntimeException("User not found"));

findById() Optional<User> qaytaradi.

Yomon uslub:

User user = userRepository.findById(1L).get();

Nega yomon? Agar user topilmasa, NoSuchElementException chiqadi.

Yaxshiroq:

User user = userRepository.findById(1L)
        .orElseThrow(() -> new RuntimeException("User not found"));

Hammasini olish

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

Lekin katta jadvalda ehtiyot bo‘lish kerak. Agar jadvalda 1 million data bo‘lsa, findAll() hammasini RAM’ga olib keladi.

Real loyihada pagination ishlatiladi.


O‘chirish

userRepository.deleteById(1L);

Yoki:

userRepository.delete(user);

4. Derived Query Methods

Derived query - method nomiga qarab Spring o‘zi query yaratadi.

Masalan:

Optional<User> findByEmail(String email);

Spring buni taxminan shunday SQLga aylantiradi:

SELECT * FROM users WHERE email = ?

Eng ko‘p ishlatiladiganlari

public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByEmail(String email);

    List<User> findByAge(Integer age);

    List<User> findByAgeGreaterThan(Integer age);

    List<User> findByAgeLessThan(Integer age);

    boolean existsByEmail(String email);

    long countByAgeGreaterThan(Integer age);

    void deleteByEmail(String email);
}

And, Or

List<User> findByFullNameAndAge(String fullName, Integer age);

SQLga yaqin ma’nosi:

SELECT * FROM users
WHERE full_name = ? AND age = ?

Yana:

List<User> findByFullNameOrEmail(String fullName, String email);

Containing, StartsWith, EndsWith

List<User> findByFullNameContaining(String text);

Bu LIKE queryga o‘xshaydi:

WHERE full_name LIKE '%text%'

Misollar:

List<User> findByFullNameStartingWith(String prefix);

List<User> findByFullNameEndingWith(String suffix);

IgnoreCase

Optional<User> findByEmailIgnoreCase(String email);

Masalan:

ALI@MAIL.COM
ali@mail.com
Ali@Mail.Com

bularni bir xil deb qidirishi mumkin.


OrderBy

List<User> findByAgeGreaterThanOrderByFullNameAsc(Integer age);

Ma’nosi:

WHERE age > ?
ORDER BY full_name ASC

Yana:

List<User> findByAgeGreaterThanOrderByCreatedAtDesc(Integer age);

5. Derived query haddan oshsa yomonlashadi

Bu method o‘qilishi qiyin:

List<User> findByFullNameContainingAndAgeGreaterThanAndEmailEndingWithOrderByCreatedAtDesc(
        String name,
        Integer age,
        String emailDomain
);

Bunday holatda @Query ishlatgan yaxshi.


6. @Query nima?

@Query - query’ni o‘zingiz yozishingiz uchun ishlatiladi.

Ikki turi bor:

JPQL query
Native SQL query

7. JPQL query

JPQL database jadval nomi bilan emas, Entity nomi bilan ishlaydi.

Entity:

@Entity
public class User {
    private Long id;
    private String fullName;
    private String email;
    private Integer age;
}

Repository:

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("select u from User u where u.age >= :age")
    List<User> findAdults(@Param("age") Integer age);
}

Bu yerda User - jadval emas, entity class nomi.

u.age - column emas, Java field nomi.


JPQL’da DTO qaytarish

Masalan response uchun faqat kerakli fieldlarni olish:

public record UserShortResponse(
        Long id,
        String fullName
) {
}

Repository:

@Query("""
       select new com.example.demo.user.UserShortResponse(u.id, u.fullName)
       from User u
       where u.age >= :age
       """)
List<UserShortResponse> findAdultUsers(@Param("age") Integer age);

Bu usul katta entity’ni to‘liq olib kelmaslik uchun foydali.


8. Native SQL query

Native query - to‘g‘ridan-to‘g‘ri SQL yozish.

@Query(value = "select * from users where age >= :age", nativeQuery = true)
List<User> findAdultsNative(@Param("age") Integer age);

Qachon kerak?

  • murakkab SQL bo‘lsa

  • database-specific function ishlatilsa

  • performance uchun maxsus query yozish kerak bo‘lsa

  • window function, CTE ishlatilsa

Lekin har doim native query yozish shart emas. Oddiy holatda derived query yoki JPQL yetadi.


9. Pagination nima?

Pagination - data’ni sahifalab olish.

Masalan hamma userlarni birdan olish yomon:

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

Agar 500 000 ta user bo‘lsa, server qiynaladi.

Shuning uchun:

1-sahifa: 20 ta user
2-sahifa: 20 ta user
3-sahifa: 20 ta user

Pageable bilan ishlash

Repository:

public interface UserRepository extends JpaRepository<User, Long> {

    Page<User> findByAgeGreaterThan(Integer age, Pageable pageable);
}

Service:

public Page<UserResponse> getUsers(int page, int size) {
    Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());

    return userRepository.findAll(pageable)
            .map(userMapper::toResponse);
}

Controller:

@GetMapping
public Page<UserResponse> getUsers(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size
) {
    return userService.getUsers(page, size);
}

Request:

GET /api/users?page=0&size=20

10. Page, Slice, List farqi

Page

Page<User> users = userRepository.findAll(pageable);

Page qo‘shimcha ma’lumot beradi:

content
totalElements
totalPages
pageNumber
pageSize
first
last

Kamchiligi: ko‘pincha qo‘shimcha count query ishlaydi.


Slice

Slice<User> users = userRepository.findByAgeGreaterThan(18, pageable);

Slice faqat keyingi sahifa bor-yo‘qligini biladi:

content
hasNext
hasPrevious

totalElements va totalPages yo‘q.

Katta data uchun Slice ba’zan yengilroq bo‘ladi.


List

List<User> users = userRepository.findByAgeGreaterThan(18, pageable);

Faqat data qaytaradi. Pagination metadata yo‘q.


11. Sorting

Sorting - tartiblash.

Sort sort = Sort.by("fullName").ascending();
List<User> users = userRepository.findAll(sort);

Yoki:

Sort sort = Sort.by("createdAt").descending();

Pagination bilan:

Pageable pageable = PageRequest.of(
        0,
        20,
        Sort.by("createdAt").descending()
);

Ko‘p field bo‘yicha sorting:

Sort sort = Sort.by("age").descending()
        .and(Sort.by("fullName").ascending());

12. Entity relationshiplar

Real loyihada entitylar bir-biri bilan bog‘langan bo‘ladi.

Masalan:

User → Orders
Order → Product
Product → Category

JPA’da asosiy relationshiplar:

@OneToOne
@OneToMany
@ManyToOne
@ManyToMany

13. @ManyToOne

Eng ko‘p ishlatiladigan relationlardan biri.

Masalan:

Bitta userda ko‘p order bo‘lishi mumkin.
Bitta order faqat bitta userga tegishli.

Bu yerda Order → User munosabati ManyToOne.

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private BigDecimal totalPrice;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

Database’da:

orders
------
id
total_price
user_id

user_id - foreign key.


@JoinColumn

@JoinColumn(name = "user_id")
private User user;

Bu orders jadvalidagi user_id column users.id bilan bog‘lanishini bildiradi.


14. @OneToMany

User tomondan qarasak:

Bitta userda ko‘p order bor.
@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String fullName;

    @OneToMany(mappedBy = "user")
    private List<Order> orders = new ArrayList<>();
}

Bu yerda:

mappedBy = "user"

ma’nosi: relation egasi Order entity ichidagi user field.


Muhim qoida

Ko‘pincha @ManyToOne tomoni relation egasi bo‘ladi.

Ya’ni foreign key qaysi jadvalda bo‘lsa, relation egasi ham odatda o‘sha tomonda.

orders.user_id bor
demak relation egasi Order.user

15. @OneToOne

Masalan:

User - UserProfile
Bitta userda bitta profile bor.
Bitta profile bitta userga tegishli.
@Entity
public class UserProfile {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String bio;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

Yoki User tomonda:

@OneToOne(mappedBy = "user")
private UserProfile profile;

16. @ManyToMany

Masalan:

Student ko‘p Course olishi mumkin.
Course’da ko‘p Student bo‘lishi mumkin.
@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String fullName;

    @ManyToMany
    @JoinTable(
            name = "student_courses",
            joinColumns = @JoinColumn(name = "student_id"),
            inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses = new ArrayList<>();
}
@Entity
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
}

Database’da oraliq jadval bo‘ladi:

student_courses
---------------
student_id
course_id

Real loyihada @ManyToManydan ehtiyot bo‘ling

Ko‘p real holatda @ManyToMany o‘rniga alohida entity ishlatgan yaxshi.

Masalan:

StudentCourse

Nega?

Chunki oraliq jadvalda qo‘shimcha fieldlar bo‘lishi mumkin:

student_id
course_id
joined_at
status
grade

Shunda bunday yoziladi:

@Entity
public class StudentCourse {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Student student;

    @ManyToOne(fetch = FetchType.LAZY)
    private Course course;

    private LocalDateTime joinedAt;

    private String status;
}

Bu usul real loyihada ancha nazoratli.


17. FetchType.LAZY va FetchType.EAGER

Bu juda muhim.

LAZY

LAZY - bog‘langan entity kerak bo‘lganda yuklanadi.

@ManyToOne(fetch = FetchType.LAZY)
private User user;

Masalan orderni oldingiz:

Order order = orderRepository.findById(1L).orElseThrow();

Bu paytda user hali yuklanmagan bo‘lishi mumkin.

Agar keyin chaqirsangiz:

order.getUser().getFullName();

shunda user yuklanadi.


EAGER

EAGER - bog‘langan entity darhol yuklanadi.

@ManyToOne(fetch = FetchType.EAGER)
private User user;

Orderni olsangiz, user ham birga keladi.

Bu qulay ko‘rinadi, lekin katta loyihada performance muammo keltiradi.


Amaliy maslahat

Ko‘pincha shunday ishlatiladi:

@ManyToOne(fetch = FetchType.LAZY)
@OneToOne(fetch = FetchType.LAZY)
@OneToMany(fetch = FetchType.LAZY)
@ManyToMany(fetch = FetchType.LAZY)

Ya’ni defaultga ishonib ketmasdan, LAZYni aniq yozish yaxshi.


18. N+1 problem

Bu JPA’da eng mashhur performance muammolardan biri.

Tasavvur qiling:

List<Order> orders = orderRepository.findAll();

for (Order order : orders) {
    System.out.println(order.getUser().getFullName());
}

Agar 100 ta order bo‘lsa:

1 ta query - orders olish uchun
100 ta query - har bir order userini olish uchun

Jami:

101 query

Bu N+1 problem deyiladi.


Yechim: fetch join

@Query("""
       select o from Order o
       join fetch o.user
       """)
List<Order> findAllWithUser();

Bu order va userni bitta query bilan olib keladi.


Yechim: EntityGraph

@EntityGraph(attributePaths = "user")
List<Order> findAll();

Bu ham userni oldindan yuklab beradi.


19. CascadeType

Cascade - bir entityga qilingan amal bog‘langan entityga ham ta’sir qilish-qilmasligini belgilaydi.

Masalan:

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();

Agar user saqlansa, orderlar ham saqlanishi mumkin.

Agar user o‘chirilsa, orderlar ham o‘chishi mumkin.


Cascade turlari

PERSIST  - save qilganda bog‘langan entity ham save bo‘ladi
MERGE    - update qilganda bog‘langan entity ham update bo‘ladi
REMOVE   - delete qilganda bog‘langan entity ham delete bo‘ladi
REFRESH  - refresh amalini uzatadi
DETACH   - detach amalini uzatadi
ALL      - hammasi

Xavfli joyi

@ManyToOne(cascade = CascadeType.ALL)
private User user;

Bu xavfli bo‘lishi mumkin.

Masalan orderni o‘chirganda user ham o‘chib ketishi mumkin.

Amaliy qoida:

@ManyToOne tomonda CascadeType.ALL ishlatishga shoshilmang.

CascadeType.REMOVEni ham ehtiyot ishlating.


20. orphanRemoval

orphanRemoval = true - parentdan olib tashlangan child database’dan ham o‘chadi.

@OneToMany(
        mappedBy = "user",
        cascade = CascadeType.ALL,
        orphanRemoval = true
)
private List<Order> orders = new ArrayList<>();

Masalan:

user.getOrders().remove(order);

Shunda order database’dan ham o‘chishi mumkin.

Bu kuchli feature, lekin ehtiyot ishlatish kerak.


21. Transaction bilan relationlar

Relationlar bilan ishlaganda @Transactional muhim.

Masalan:

@Transactional
public OrderResponse createOrder(Long userId, CreateOrderRequest request) {
    User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("User not found"));

    Order order = new Order();
    order.setUser(user);
    order.setTotalPrice(request.totalPrice());

    Order savedOrder = orderRepository.save(order);

    return orderMapper.toResponse(savedOrder);
}

Nega service’da @Transactional yaxshi?

Chunki bitta business operation ichida:

Userni topish
Order yaratish
Orderni saqlash
Response tayyorlash

hammasi bir transaction doirasida bo‘ladi.


22. Update qilishda ehtiyot bo‘lish

Yomonroq usul:

public User update(Long id, User request) {
    request.setId(id);
    return userRepository.save(request);
}

Bu xavfli. Chunki request ichida kelmagan fieldlar null bo‘lib qolishi mumkin.

Yaxshiroq:

@Transactional
public UserResponse update(Long id, UserUpdateRequest request) {
    User user = userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));

    user.setFullName(request.fullName());
    user.setAge(request.age());

    return userMapper.toResponse(user);
}

Bu yerda save() chaqirmasangiz ham bo‘ladi, chunki entity transaction ichida managed holatda. Hibernate o‘zgarishni kuzatadi va transaction oxirida update qiladi.

Bu dirty checking deyiladi.


23. Dirty Checking nima?

Dirty checking - Hibernate entitydagi o‘zgarishlarni avtomatik aniqlashi.

@Transactional
public void changeEmail(Long userId, String newEmail) {
    User user = userRepository.findById(userId)
            .orElseThrow();

    user.setEmail(newEmail);
}

Bu yerda save() yo‘q.

Lekin transaction tugaganda Hibernate taxminan shunday SQL bajaradi:

UPDATE users SET email = ? WHERE id = ?

Bu faqat entity managed holatda va transaction ichida bo‘lsa ishlaydi.


24. DTO projection

Ba’zan entityni to‘liq olish kerak emas.

Masalan faqat:

id
fullName
email

kerak.

Interface projection:

public interface UserView {
    Long getId();
    String getFullName();
    String getEmail();
}

Repository:

List<UserView> findByAgeGreaterThan(Integer age);

Bu response uchun yengilroq bo‘lishi mumkin.


25. Amaliy Product + Category misol

Category entity

@Entity
@Table(name = "categories")
public class Category {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

Product entity

@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private BigDecimal price;

    private Integer quantity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;
}

ProductRepository

public interface ProductRepository extends JpaRepository<Product, Long> {

    List<Product> findByNameContainingIgnoreCase(String name);

    Page<Product> findByCategoryId(Long categoryId, Pageable pageable);

    boolean existsByName(String name);

    @Query("""
           select p from Product p
           join fetch p.category
           where p.id = :id
           """)
    Optional<Product> findByIdWithCategory(@Param("id") Long id);
}

ProductResponse

public record ProductResponse(
        Long id,
        String name,
        BigDecimal price,
        Integer quantity,
        Long categoryId,
        String categoryName
) {
}

Mapper

@Component
public class ProductMapper {

    public ProductResponse toResponse(Product product) {
        Category category = product.getCategory();

        return new ProductResponse(
                product.getId(),
                product.getName(),
                product.getPrice(),
                product.getQuantity(),
                category != null ? category.getId() : null,
                category != null ? category.getName() : null
        );
    }
}

26. Javachi uchun eng muhim qoidalar

Repository’da

Repository faqat database bilan ishlasin:

public interface ProductRepository extends JpaRepository<Product, Long> {
}

Business logic yozmang.


Service’da

Service business operationni boshqarsin:

@Transactional
public ProductResponse create(ProductCreateRequest request) {
    // validate
    // find category
    // create product
    // save
    // return response
}

Controller’da

Controller faqat request/response bilan ishlasin:

@PostMapping
public ProductResponse create(@RequestBody ProductCreateRequest request) {
    return productService.create(request);
}

27. Ko‘p uchraydigan xatolar

Xato 1: findAll()ni doim ishlatish

List<Product> products = productRepository.findAll();

Katta jadvalda yomon.

Yaxshiroq:

Page<Product> products = productRepository.findAll(pageable);

Xato 2: Entity’ni API response qilish

@GetMapping
public List<Product> getAll() {
    return productRepository.findAll();
}

Yaxshiroq:

@GetMapping
public Page<ProductResponse> getAll(...) {
    return productService.getAll(...);
}

Xato 3: EAGERni ko‘p ishlatish

@ManyToOne(fetch = FetchType.EAGER)
private Category category;

Kichik loyihada sezilmasligi mumkin. Lekin katta loyihada ortiqcha query va memory muammo beradi.

Yaxshiroq:

@ManyToOne(fetch = FetchType.LAZY)
private Category category;

Xato 4: Har joyga CascadeType.ALL qo‘yish

@ManyToOne(cascade = CascadeType.ALL)
private Category category;

Bu category’ni noto‘g‘ri update yoki delete qilib yuborishi mumkin.


Xato 5: Update’da yangi entity yasab save qilish

Product product = new Product();
product.setId(id);
product.setName(request.name());
productRepository.save(product);

Bu eski fieldlarni yo‘qotishi mumkin.

Yaxshiroq:

Product product = productRepository.findById(id).orElseThrow();
product.setName(request.name());
product.setPrice(request.price());

28. Amaliy vazifa

Oldingi Product CRUD API loyihasiga Category qo‘shing.

Entitylar

Category
- id
- name

Product
- id
- name
- price
- quantity
- category

Endpointlar

POST   /api/categories
GET    /api/categories

POST   /api/products
GET    /api/products?page=0&size=20
GET    /api/products/{id}
GET    /api/products?categoryId=1
PUT    /api/products/{id}
DELETE /api/products/{id}

Talablar

Product entity Category bilan @ManyToOne orqali bog‘lansin.
Product response ichida categoryId va categoryName bo‘lsin.
GET /api/products pagination bilan ishlasin.
Repository’da findByCategoryId ishlating.
Service methodlarga @Transactional qo‘ying.
Entity’ni to‘g‘ridan-to‘g‘ri response qilmang.

Xulosa

Spring Data JPA deeper mavzusida Java developer quyidagilarni yaxshi bilishi kerak:

JpaRepository CRUD methodlari
Derived query methods
@Query bilan JPQL va native SQL
Pagination va sorting
Page, Slice, List farqi
@OneToOne, @OneToMany, @ManyToOne, @ManyToMany
@JoinColumn
FetchType.LAZY vs EAGER
CascadeType xavflari
N+1 problem
@Transactional va dirty checking
DTO projection

Eng muhim amaliy qoida:

Controller API bilan ishlaydi.
Service business logic va transactionni boshqaradi.
Repository database bilan ishlaydi.
Entity database modeli.
DTO API modeli.