Data Access & ORM

30.06.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 31 daqiqa o'qish

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 lock

Bu 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
   ↓
Database

Masalan:

@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        ↔  column

3. 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 qismi

Biz 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/hashCode relationshiplarni 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
@ManyToMany

9. @ManyToOne

Ko‘p order bitta userga tegishli.

users 1 ----- * orders

Entity:

@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
UserRole

Nega?

Chunki keyin join table’da qo‘shimcha fieldlar kerak bo‘lib qoladi:

created_at
assigned_by
status

15. 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 yaxshi

Ayniqsa:

@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 Session

Sabab: 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 uchun

Jami:

101 query

Shu N+1 problem.


20. N+1 qanday aniqlanadi?

SQL logni yoq:

spring:
  jpa:
    show-sql: true

Lekin productionga yaxshi emas.

Developmentda yaxshiroq:

logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.orm.jdbc.bind: trace

Ko‘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: 100

Yoki 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‘q

Criteria 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=20

34. 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

PERSIST

save child

MERGE

update child

REMOVE

delete child

REFRESH

refresh child

DETACH

detach child

ALL

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.sql

V1__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/migration

42. Migration qoidalari

Muhim qoidalar:

  1. Ishlab turgan migration file’ni o‘zgartirma.

  2. Yangi o‘zgarish uchun yangi migration yarat.

  3. Migration nomi aniq bo‘lsin.

  4. Production DBda qo‘lda schema o‘zgartirma.

  5. Index qo‘shish katta table’da ehtiyot qilinadi.

  6. Rollback strategiyani oldindan o‘yla.

Yomon:

V1__init.sql faylini productiondan keyin o‘zgartirish

Yaxshi:

V4__add_status_to_orders.sql

43. 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 data

45. 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

@Cacheable

Cache’da bo‘lsa method ishlamaydi

@CachePut

Method doim ishlaydi, natija cachega yoziladi

@CacheEvict

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
        ↓
      Redis

Spring Boot Redis dependency:

implementation("org.springframework.boot:spring-boot-starter-data-redis")

YAML:

spring:
  data:
    redis:
      host: localhost
      port: 6379

Cache config:

spring:
  cache:
    type: redis

49. 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 stampede

TTL 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: false

Agar 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: false

qilib, kerakli datani service layerda aniq yuklash yaxshi.


51. Real production structure

order
 ├── controller
 ├── service
 ├── repository
 ├── entity
 ├── dto
 │    ├── request
 │    └── response
 ├── mapper
 ├── specification
 ├── migration
 └── exception

52. 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 qilish

Yaxshi:

Flyway/Liquibase migration bilan boshqarish

57. 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 muammosi

Qisqa 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