Data Access & ORM - Java backend’da database bilan to‘g‘ri ishlash qismi. Java developer uchun bu juda muhim, chunki production’dagi ko‘p muammolar aynan shu joydan chiqadi:
slow query
N+1 problem
transaction xatolari
lazy loading exception
index ishlamasligi
cache noto‘g‘ri ishlashi
migration conflict
database lockBu bo‘limga quyidagilar kiradi: JPA / Hibernate entities, relationships, N+1 problem, fetch strategies, JPQL, Criteria API, Flyway / Liquibase migrations, caching: Spring Cache, Ehcache, Redis.
1. Data Access nima?
Data Access - application database bilan qanday gaplashishini bildiradi.
Java/Spring’da odatda flow shunday:
Controller
↓
Service
↓
Repository
↓
DatabaseMasalan:
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public UserDto getById(@PathVariable Long id) {
return userService.getById(id);
}
}@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserDto getById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User topilmadi"));
return UserDto.from(user);
}
}public interface UserRepository extends JpaRepository<User, Long> {
}2. ORM nima?
ORM - Object Relational Mapping.
Ya’ni Java object bilan database table orasidagi mapping.
Java class:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}Database table:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
);ORM nima qiladi?
Java object ↔ Database row
User class ↔ users table
field ↔ column3. JPA va Hibernate farqi
Bu interview’da ko‘p so‘raladi.
Tushuncha | Ma’nosi |
|---|---|
JPA | Specification, ya’ni qoida/interface |
Hibernate | JPA’ning implementation’i |
Spring Data JPA | Repository abstraction, JPA bilan ishlashni osonlashtiradi |
Soddaroq:
JPA - qoidalar
Hibernate - shu qoidalarni bajaruvchi engine
Spring Data JPA - repository yozishni osonlashtiruvchi Spring qismiBiz ko‘pincha quyidagini ishlatamiz:
public interface UserRepository extends JpaRepository<User, Long> {
}Lekin ichkarida Hibernate SQL generate qiladi.
4. Entity nima?
Entity - database table’ga mos Java class.
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
}Bu class products table bilan bog‘lanadi.
5. Entity yozishda muhim qoidalar
1. No-args constructor kerak
Hibernate object yaratishi uchun kerak.
protected Product() {
}Yoki Lombok:
@NoArgsConstructor(access = AccessLevel.PROTECTED)2. Entity’da setterlarni ehtiyot ishlatish
Yomon:
product.setPrice(newPrice);
product.setStatus(status);
product.setName(name);Har joydan field o‘zgarib ketadi.
Yaxshiroq:
public void changePrice(BigDecimal newPrice) {
if (newPrice.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Price musbat bo‘lishi kerak");
}
this.price = newPrice;
}Business qoida entity ichida bo‘ladi.
3. equals() va hashCode() ehtiyot
Entity’da equals/hashCode noto‘g‘ri yozilsa, Hibernate va collectionlarda g‘alati bug chiqadi.
Ko‘p hollarda id asosida yoziladi, lekin yangi entity’da id == null bo‘lishini hisobga olish kerak.
Lombok @Datani entity’da ishlatish xavfli.
Yomon:
@Data
@Entity
public class User {
@Id
private Long id;
@OneToMany(mappedBy = "user")
private List<Order> orders;
}Nega yomon?
toString()relationshiplarni chaqirib yuboradi;lazy loading ishlashi mumkin;
recursive call bo‘lishi mumkin;
equals/hashCoderelationshiplarni ham ishlatishi mumkin.
Yaxshiroq:
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {
@Id
private Long id;
private String name;
}6. Repository nima?
Repository - database bilan ishlash layer.
public interface UserRepository extends JpaRepository<User, Long> {
}JpaRepository tayyor methodlar beradi:
findById()
findAll()
save()
delete()
existsById()
count()7. Query methodlar
Spring Data JPA method nomidan query yaratadi.
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByStatus(UserStatus status);
boolean existsByEmail(String email);
List<User> findByNameContainingIgnoreCase(String name);
}Spring buni SQLga aylantiradi.
Misol:
Optional<User> findByEmail(String email);Taxminan:
SELECT * FROM users WHERE email = ?;8. Relationshiplar
Database’da tablelar bir-biriga bog‘lanadi. JPA’da buni relationship annotationlar bilan yozamiz.
Asosiy turlar:
@OneToOne
@OneToMany
@ManyToOne
@ManyToMany9. @ManyToOne
Ko‘p order bitta userga tegishli.
users 1 ----- * ordersEntity:
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private BigDecimal totalAmount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}Bu yerda orders.user_id column users.id ga ulanadi.
Database:
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
total_amount NUMERIC(19, 2),
user_id BIGINT REFERENCES users(id)
);10. @OneToMany
Bitta userda ko‘p order bor.
@Entity
@Table(name = "users")
public class User {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Order> orders = new ArrayList<>();
}mappedBy = "user" degani: relationship egasi Order classidagi user field.
11. Owning side nima?
JPA’da relationshipda bitta tomon owning side bo‘ladi.
Owning side - foreign key’ni boshqaradigan tomon.
Misolda:
@ManyToOne
@JoinColumn(name = "user_id")
private User user;Order owning side. Chunki user_id column orders table’da turibdi.
User.orders esa inverse side:
@OneToMany(mappedBy = "user")
private List<Order> orders;12. Relationship helper method
Bidirectional relationshipda ikki tomonni ham to‘g‘ri set qilish kerak.
@Entity
public class User {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
public void addOrder(Order order) {
orders.add(order);
order.assignUser(this);
}
public void removeOrder(Order order) {
orders.remove(order);
order.assignUser(null);
}
}@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
private User user;
public void assignUser(User user) {
this.user = user;
}
}Bu consistency uchun kerak.
13. @OneToOne
Masalan, user va passport.
users 1 ----- 1 passports@Entity
public class UserProfile {
@Id
private Long id;
private String phone;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}14. @ManyToMany
Masalan, user va role.
users * ----- * roles@Entity
public class User {
@ManyToMany
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}Lekin real loyihada @ManyToManyni ehtiyot ishlatish kerak.
Ko‘pincha join table’ni alohida entity qilish yaxshiroq:
User
Role
UserRoleNega?
Chunki keyin join table’da qo‘shimcha fieldlar kerak bo‘lib qoladi:
created_at
assigned_by
status15. FetchType: LAZY vs EAGER
Bu juda muhim.
EAGER
Relationship darhol yuklanadi.
@ManyToOne(fetch = FetchType.EAGER)
private User user;Order olsak, user ham avtomatik olinadi.
LAZY
Relationship kerak bo‘lganda yuklanadi.
@ManyToOne(fetch = FetchType.LAZY)
private User user;Order olganda user darhol olinmaydi. order.getUser() chaqirilganda olinadi.
16. Qaysi biri yaxshi?
Ko‘p holatda:
LAZY default qilib ishlatish yaxshiAyniqsa:
@OneToMany(fetch = FetchType.LAZY)
@ManyToMany(fetch = FetchType.LAZY)EAGER ko‘p joyda ortiqcha query va performance muammosi chiqaradi.
17. LazyInitializationException
Ko‘p uchraydigan xato:
failed to lazily initialize a collection
could not initialize proxy - no SessionSabab: Hibernate session yopilganidan keyin lazy fieldni chaqirding.
Misol:
public UserDto getUser(Long id) {
User user = userRepository.findById(id).orElseThrow();
return new UserDto(
user.getId(),
user.getName(),
user.getOrders().size()
);
}Agar transaction/session yopilgan bo‘lsa, user.getOrders() xato beradi.
18. LazyInitializationException yechimlari
1. Transaction ichida kerakli datani olish
@Transactional(readOnly = true)
public UserDto getUser(Long id) {
User user = userRepository.findById(id).orElseThrow();
return UserDto.from(user);
}Lekin bu har doim ham yetarli emas. N+1 chiqishi mumkin.
2. Fetch join ishlatish
@Query("""
select u
from User u
left join fetch u.orders
where u.id = :id
""")
Optional<User> findByIdWithOrders(Long id);3. DTO projection ishlatish
@Query("""
select new uz.example.UserSummaryDto(u.id, u.name, count(o.id))
from User u
left join u.orders o
where u.id = :id
group by u.id, u.name
""")
Optional<UserSummaryDto> findSummaryById(Long id);Ko‘p holatda API response uchun DTO projection yaxshi.
19. N+1 problem
N+1 - ORM’dagi eng mashhur performance muammo.
Misol:
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
System.out.println(order.getUser().getName());
}Agar 100 ta order bo‘lsa:
1 query orders uchun
100 query har bir order.user uchunJami:
101 queryShu N+1 problem.
20. N+1 qanday aniqlanadi?
SQL logni yoq:
spring:
jpa:
show-sql: trueLekin productionga yaxshi emas.
Developmentda yaxshiroq:
logging:
level:
org.hibernate.SQL: debug
org.hibernate.orm.jdbc.bind: traceKo‘rasan: bitta endpoint bosilganda 1 query o‘rniga 50-100 query ketayotgan bo‘ladi.
21. N+1 yechim: fetch join
@Query("""
select o
from Order o
join fetch o.user
""")
List<Order> findAllWithUser();Endi orders va users bitta queryda keladi.
Taxminiy SQL:
SELECT o.*, u.*
FROM orders o
JOIN users u ON o.user_id = u.id;22. N+1 yechim: EntityGraph
@EntityGraph(attributePaths = {"user"})
@Query("select o from Order o")
List<Order> findAllWithUser();Bu ham relationshipni oldindan yuklaydi.
23. N+1 yechim: Batch size
spring:
jpa:
properties:
hibernate.default_batch_fetch_size: 100Yoki annotation:
@BatchSize(size = 100)
@OneToMany(mappedBy = "user")
private List<Order> orders;Bu har bitta lazy relation uchun alohida query yuborish o‘rniga batch qilib oladi.
24. DTO Projection
API uchun har doim entity qaytarish yaxshi emas.
Yomon:
@GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll();
}Muammo:
lazy loading bug;
sensitive field chiqib ketishi mumkin;
entity structure API bilan bog‘lanib qoladi;
JSON recursion bo‘lishi mumkin;
ortiqcha data ketadi.
Yaxshi:
public record UserDto(
Long id,
String name,
String email
) {
}@GetMapping("/users")
public List<UserDto> getUsers() {
return userService.getUsers();
}25. Interface Projection
public interface UserView {
Long getId();
String getName();
String getEmail();
}Repository:
List<UserView> findByStatus(UserStatus status);Spring faqat kerakli fieldlarni olishi mumkin.
26. Constructor Projection
public record UserListItemDto(
Long id,
String name
) {
}@Query("""
select new uz.example.UserListItemDto(u.id, u.name)
from User u
where u.status = :status
""")
List<UserListItemDto> findListItemsByStatus(UserStatus status);Bu API list response uchun juda yaxshi.
27. JPQL
JPQL - SQLga o‘xshaydi, lekin table emas, entity bilan ishlaydi.
SQL:
SELECT * FROM users WHERE status = 'ACTIVE';JPQL:
@Query("select u from User u where u.status = :status")
List<User> findActiveUsers(UserStatus status);Bu yerda User - table emas, entity class.
28. Native Query
Agar murakkab SQL kerak bo‘lsa:
@Query(
value = """
select *
from users
where status = :status
""",
nativeQuery = true
)
List<User> findByStatusNative(String status);Native query qachon kerak?
DB-specific function;
window function;
recursive query;
performance-critical query;
JPQL bilan noqulay bo‘lgan joy.
Lekin haddan oshirib yubormaslik kerak. Native query portability’ni kamaytiradi.
29. Criteria API
Criteria API - query’ni Java code bilan dinamik qurish.
Masalan, filterlar optional bo‘lsa:
name bor yoki yo‘q
status bor yoki yo‘q
fromDate bor yoki yo‘q
toDate bor yoki yo‘qCriteria ishlatish mumkin, lekin ko‘p hollarda code verbose bo‘ladi.
Spring Data Specification ko‘proq qulay.
30. Specification
Entity:
public class UserSpecifications {
public static Specification<User> hasStatus(UserStatus status) {
return (root, query, cb) ->
status == null ? null : cb.equal(root.get("status"), status);
}
public static Specification<User> nameContains(String name) {
return (root, query, cb) ->
name == null ? null : cb.like(
cb.lower(root.get("name")),
"%" + name.toLowerCase() + "%"
);
}
}Repository:
public interface UserRepository
extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}Service:
Specification<User> spec = Specification
.where(UserSpecifications.hasStatus(status))
.and(UserSpecifications.nameContains(name));
Page<User> users = userRepository.findAll(spec, pageable);Bu dinamik filter uchun foydali.
31. Pagination
Katta table’da findAll() xavfli.
Yomon:
List<User> users = userRepository.findAll();Yaxshi:
Page<User> users = userRepository.findAll(PageRequest.of(0, 20));Controller:
@GetMapping
public Page<UserDto> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size
) {
return userService.getUsers(page, size);
}Service:
@Transactional(readOnly = true)
public Page<UserDto> getUsers(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
return userRepository.findAll(pageable)
.map(UserDto::from);
}32. Offset pagination muammosi
SELECT *
FROM orders
ORDER BY id DESC
LIMIT 20 OFFSET 100000;Katta offset bo‘lsa, DB oldingi 100000 qatorni sanab o‘tadi.
Katta table uchun bu sekinlashadi.
33. Keyset pagination
Katta jadval uchun yaxshiroq.
SELECT *
FROM orders
WHERE id < :lastId
ORDER BY id DESC
LIMIT 20;Repository:
@Query("""
select o
from Order o
where (:lastId is null or o.id < :lastId)
order by o.id desc
""")
List<Order> findNextPage(Long lastId, Pageable pageable);Service:
public List<OrderDto> getNextPage(Long lastId, int size) {
Pageable pageable = PageRequest.of(0, size);
return orderRepository.findNextPage(lastId, pageable)
.stream()
.map(OrderDto::from)
.toList();
}API:
GET /orders?lastId=1500&size=2034. Transaction va ORM
Hibernate entitylarni persistence context ichida kuzatadi.
@Transactional
public void changeName(Long id, String newName) {
User user = userRepository.findById(id).orElseThrow();
user.changeName(newName);
}Bu yerda save() chaqirilmagan. Lekin transaction commit bo‘lganda Hibernate o‘zgarishni detect qilib update qiladi.
Bu dirty checking deyiladi.
35. Dirty Checking
@Transactional
public void updateUser(Long id, String name) {
User user = userRepository.findById(id).orElseThrow();
user.changeName(name);
}Hibernate transaction ichida entityni kuzatadi:
old state: name = Ali
new state: name = Vali
commit payti: UPDATE users SET name = 'Vali'Shuning uchun har doim save() qilish shart emas, agar entity managed bo‘lsa.
36. Managed vs Detached Entity
Managed
Entity persistence context ichida.
@Transactional
public void update(Long id) {
User user = userRepository.findById(id).orElseThrow();
user.changeName("Ali");
}user managed.
Detached
Entity sessiondan tashqarida.
User user = userRepository.findById(id).orElseThrow();
// transaction tugadi
user.changeName("Ali");Endi Hibernate uni kuzatmaydi.
Detached entityni update qilish uchun merge/save kerak bo‘lishi mumkin.
37. Cascade
Cascade - parent entitydagi amal child entitylarga ham qo‘llanadimi yoki yo‘q.
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> items = new ArrayList<>();Agar order save bo‘lsa, itemlar ham save bo‘ladi.
Cascade turlari:
Cascade | Ma’nosi |
|---|---|
| save child |
| update child |
| delete child |
| refresh child |
| detach child |
| hammasi |
CascadeType.ALLni hamma joyda ishlatish xavfli. Ayniqsa ManyToOne tomonida ishlatmaslik kerak.
38. orphanRemoval
Agar child parent collection’dan olib tashlansa, DBdan ham o‘chsin.
@OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<OrderItem> items = new ArrayList<>();Misol:
order.removeItem(item);Natija: order_itemsdan item o‘chadi.
Bu order-item kabi composition relationlarda foydali.
39. Index
ORM ishlatsak ham, DB indexni tushunishimiz kerak.
Query:
SELECT *
FROM users
WHERE email = 'ali@example.com';Agar email index bo‘lmasa, katta table’da sekin bo‘ladi.
Migration:
CREATE INDEX idx_users_email ON users(email);Unique bo‘lsa:
CREATE UNIQUE INDEX uq_users_email ON users(email);JPA annotation:
@Table(
name = "users",
indexes = {
@Index(name = "idx_users_email", columnList = "email")
}
)Lekin real loyihada indexlarni migration orqali boshqarish yaxshiroq.
40. Flyway Migration
Flyway - database schema o‘zgarishlarini version bilan boshqaradi.
Filelar:
db/migration
├── V1__create_users_table.sql
├── V2__create_orders_table.sql
└── V3__add_index_to_users_email.sqlV1__create_users_table.sql:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);V2__create_orders_table.sql:
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
total_amount NUMERIC(19, 2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);Flyway app start bo‘lganda migrationlarni ketma-ket ishlatadi.
41. Flyway config
Gradle dependency:
implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-database-postgresql")YAML:
spring:
flyway:
enabled: true
locations: classpath:db/migration42. Migration qoidalari
Muhim qoidalar:
Ishlab turgan migration file’ni o‘zgartirma.
Yangi o‘zgarish uchun yangi migration yarat.
Migration nomi aniq bo‘lsin.
Production DBda qo‘lda schema o‘zgartirma.
Index qo‘shish katta table’da ehtiyot qilinadi.
Rollback strategiyani oldindan o‘yla.
Yomon:
V1__init.sql faylini productiondan keyin o‘zgartirishYaxshi:
V4__add_status_to_orders.sql43. Liquibase
Liquibase ham migration tool. SQL, XML, YAML, JSON formatlarda ishlaydi.
Misol YAML:
databaseChangeLog:
- changeSet:
id: 1
author: ali
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGSERIAL
constraints:
primaryKey: true
- column:
name: name
type: VARCHAR(255)Flyway oddiyroq. Liquibase murakkab enterprise loyihalarda kuchliroq.
44. Caching
Cache - tez-tez kerak bo‘ladigan datani vaqtincha saqlash.
Masalan:
settings
exchange rates
product categories
user permissions
reference data45. Spring Cache
Dependency:
implementation("org.springframework.boot:spring-boot-starter-cache")Enable:
@EnableCaching
@Configuration
public class CacheConfig {
}Ishlatish:
@Cacheable(value = "users", key = "#id")
public UserDto getUser(Long id) {
return userRepository.findById(id)
.map(UserDto::from)
.orElseThrow();
}Birinchi chaqirilganda DBdan oladi. Keyingi chaqirilganda cachedan qaytaradi.
46. @CacheEvict
Data o‘zgarsa, cache tozalanishi kerak.
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}Update payti:
@CacheEvict(value = "users", key = "#id")
@Transactional
public UserDto updateUser(Long id, UpdateUserRequest request) {
User user = userRepository.findById(id).orElseThrow();
user.changeName(request.name());
return UserDto.from(user);
}47. @CachePut
Methodni bajaradi va natijani cachega yozadi.
@CachePut(value = "users", key = "#id")
@Transactional
public UserDto updateUser(Long id, UpdateUserRequest request) {
User user = userRepository.findById(id).orElseThrow();
user.changeName(request.name());
return UserDto.from(user);
}Farqi:
Annotation | Ma’nosi |
|---|---|
| Cache’da bo‘lsa method ishlamaydi |
| Method doim ishlaydi, natija cachega yoziladi |
| Cache’dan o‘chiradi |
48. Redis Cache
Agar bir nechta app instance bo‘lsa, local cache yetmaydi. Redis ishlatiladi.
App instance 1
App instance 2
App instance 3
↓
RedisSpring Boot Redis dependency:
implementation("org.springframework.boot:spring-boot-starter-data-redis")YAML:
spring:
data:
redis:
host: localhost
port: 6379Cache config:
spring:
cache:
type: redis49. Cache xavflari
Cache performance beradi, lekin bug ham keltiradi.
Eng ko‘p xatolar:
stale data
cache invalidation yo‘qligi
TTL yo‘qligi
key noto‘g‘ri tanlanishi
sensitive data cachega tushishi
juda katta object cachelash
cache stampedeTTL kerak
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}50. Open Session in View
Spring Boot’da open-in-view degan setting bor.
spring:
jpa:
open-in-view: falseAgar true bo‘lsa, Hibernate session view/controllergacha ochiq turadi. Bu LazyInitializationExceptionni yashirishi mumkin, lekin controller yoki JSON serialization paytida query ketib qoladi.
Middle-level production’da ko‘pincha:
spring:
jpa:
open-in-view: falseqilib, kerakli datani service layerda aniq yuklash yaxshi.
51. Real production structure
order
├── controller
├── service
├── repository
├── entity
├── dto
│ ├── request
│ └── response
├── mapper
├── specification
├── migration
└── exception52. Real misol: Order entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<OrderItem> items = new ArrayList<>();
public Order(User user) {
this.user = user;
this.status = OrderStatus.NEW;
this.totalAmount = BigDecimal.ZERO;
}
public void addItem(Product product, int quantity) {
OrderItem item = new OrderItem(this, product, quantity);
items.add(item);
recalculateTotal();
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
public void confirm() {
if (items.isEmpty()) {
throw new IllegalStateException("Order item bo‘sh bo‘lmasin");
}
this.status = OrderStatus.CONFIRMED;
}
}53. OrderItem entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer quantity;
private BigDecimal price;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
public OrderItem(Order order, Product product, int quantity) {
this.order = order;
this.product = product;
this.quantity = quantity;
this.price = product.getPrice();
}
public BigDecimal getTotalPrice() {
return price.multiply(BigDecimal.valueOf(quantity));
}
}54. Repository fetch join
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("""
select distinct o
from Order o
left join fetch o.items i
left join fetch i.product
where o.id = :id
""")
Optional<Order> findByIdWithItems(Long id);
}Bu order detail page uchun kerakli datani bitta queryda olib keladi.
55. Service
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;
public OrderService(
OrderRepository orderRepository,
UserRepository userRepository,
ProductRepository productRepository
) {
this.orderRepository = orderRepository;
this.userRepository = userRepository;
this.productRepository = productRepository;
}
@Transactional
public OrderDto createOrder(CreateOrderRequest request) {
User user = userRepository.findById(request.userId())
.orElseThrow(() -> new RuntimeException("User topilmadi"));
Order order = new Order(user);
for (CreateOrderItemRequest itemRequest : request.items()) {
Product product = productRepository.findById(itemRequest.productId())
.orElseThrow(() -> new RuntimeException("Product topilmadi"));
order.addItem(product, itemRequest.quantity());
}
order.confirm();
Order saved = orderRepository.save(order);
return OrderDto.from(saved);
}
@Transactional(readOnly = true)
public OrderDetailDto getDetail(Long id) {
Order order = orderRepository.findByIdWithItems(id)
.orElseThrow(() -> new RuntimeException("Order topilmadi"));
return OrderDetailDto.from(order);
}
}56. Ko‘p uchraydigan xatolar
1. Entity’ni API’da qaytarish
Yomon:
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow();
}Yaxshi:
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.getById(id);
}2. findAll()ni katta table’da ishlatish
Yomon:
List<Order> orders = orderRepository.findAll();Yaxshi:
Page<Order> orders = orderRepository.findAll(pageable);3. Hamma relationshipni EAGER qilish
Yomon:
@OneToMany(fetch = FetchType.EAGER)
private List<Order> orders;Bu katta query va memory muammosiga olib keladi.
Yaxshi:
@OneToMany(fetch = FetchType.LAZY)
private List<Order> orders;Kerak bo‘lsa fetch join bilan olib kel.
4. Transaction ichida tashqi API chaqirish
Yomon:
@Transactional
public void createOrder() {
orderRepository.save(order);
paymentClient.pay();
}Tashqi API sekin bo‘lsa, DB transaction uzoq ochiq turadi.
Yaxshi:
@Transactional
public void createOrder() {
orderRepository.save(order);
eventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));
}Keyin AFTER_COMMIT event bilan payment/notification.
5. Migrationni qo‘lda o‘zgartirish
Yomon:
production DBda ALTER TABLE qo‘lda qilishYaxshi:
Flyway/Liquibase migration bilan boshqarish57. Interview savollari
JPA va Hibernate farqi?
JPA - specification. Hibernate - implementation.
Entity nima?
Database table’ga mapping qilingan Java class.
@ManyToOne va @OneToMany farqi?
@ManyToOne: ko‘p child bitta parentga tegishli.@OneToMany: bitta parentda ko‘p child bor.
Owning side nima?
Foreign key’ni boshqaradigan relationship tomoni.
LAZY va EAGER farqi?
LAZY - kerak bo‘lganda yuklaydi.
EAGER - entity bilan birga darhol yuklaydi.
N+1 problem nima?
Bitta asosiy querydan keyin har bir row uchun qo‘shimcha query ketishi.
N+1 qanday yechiladi?
Fetch join, EntityGraph, batch size, DTO projection.
Dirty checking nima?
Transaction ichida managed entity o‘zgarsa, Hibernate commit payti avtomatik update qiladi.
Cascade nima?
Parentdagi operation child entitylarga ham qo‘llanishi.
orphanRemoval nima?
Parent collectiondan olib tashlangan child DBdan ham o‘chiriladi.
Flyway nima?
Database migrationlarni version bilan boshqaruvchi tool.
Cache qachon kerak?
Tez-tez o‘qiladigan, kam o‘zgaradigan data uchun.
58. Java Developer checklist
Data Access & ORM bo‘yicha biz quyidagilarni bilishimiz kerak:
JPA vs Hibernate farqi
Entity yozish qoidalari
Repository query methodlar
@OneToMany / @ManyToOne / @OneToOne / @ManyToMany
Owning side
LAZY vs EAGER
LazyInitializationException
N+1 problem
fetch join
EntityGraph
DTO projection
JPQL
native query
Specification
pagination
keyset pagination
dirty checking
managed/detached entity
cascade
orphanRemoval
index
Flyway/Liquibase migration
Spring Cache / Redis cache
open-in-view muammosiQisqa xulosa
Data Access & ORM - Java developer uchun eng muhim mavzulardan biri.
Junior odatda shunday yozadi:
repository.findAll();
repository.save(entity);Middle esa shuni ham o‘ylaydi:
Bu nechta query yuboradi?
N+1 chiqmayaptimi?
Transaction qayerda ochilgan?
Entity managedmi detachedmi?
LAZY field qachon yuklanadi?
Index bormi?
Migration qayerda?
Cache invalidation bormi?
API entity qaytaryaptimi yoki DTO?Eng muhim 5 ta joy:
1. Relationshiplarni to‘g‘ri yozish
2. LAZY/EAGER va N+1ni tushunish
3. Transaction + dirty checking
4. DTO projection va pagination
5. Migration + cache