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 → DatabaseSiz 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 tipi3. 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.Combularni bir xil deb qidirishi mumkin.
OrderBy
List<User> findByAgeGreaterThanOrderByFullNameAsc(Integer age);Ma’nosi:
WHERE age > ?
ORDER BY full_name ASCYana:
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 query7. 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 userPageable 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=2010. Page, Slice, List farqi
Page
Page<User> users = userRepository.findAll(pageable);Page qo‘shimcha ma’lumot beradi:
content
totalElements
totalPages
pageNumber
pageSize
first
lastKamchiligi: 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
hasPrevioustotalElements 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 → CategoryJPA’da asosiy relationshiplar:
@OneToOne
@OneToMany
@ManyToOne
@ManyToMany13. @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_iduser_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.user15. @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_idReal loyihada @ManyToManydan ehtiyot bo‘ling
Ko‘p real holatda @ManyToMany o‘rniga alohida entity ishlatgan yaxshi.
Masalan:
StudentCourseNega?
Chunki oraliq jadvalda qo‘shimcha fieldlar bo‘lishi mumkin:
student_id
course_id
joined_at
status
gradeShunda 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 uchunJami:
101 queryBu 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 - hammasiXavfli 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 tayyorlashhammasi 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
emailkerak.
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
- categoryEndpointlar
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 projectionEng muhim amaliy qoida:
Controller API bilan ishlaydi.
Service business logic va transactionni boshqaradi.
Repository database bilan ishlaydi.
Entity database modeli.
DTO API modeli.