Spring Data Advanced

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

Bu bo‘limda oddiy JpaRepositorydan yuqoriroq darajaga chiqamiz. Spring Data advanced mavzusi quyidagilarni qamrab oladi: Specifications & Criteria API, Projections, Auditing, Optimistic locking, Spring Data Envers, va QueryDSL integration.


1. Spring Data advanced nima?

Boshlang'ich darajada biz odatda shuni bilamiz:

public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByName(String name);
}

Bu yaxshi. Lekin real project kattalashganda oddiy findBy... yetmay qoladi.

Masalan:

Userlarni filter qilish kerak:
- name bo‘yicha
- phone bo‘yicha
- status bo‘yicha
- createdDate oralig‘i bo‘yicha
- role bo‘yicha
- city bo‘yicha
- faqat active userlar
- pagination bilan

Buni faqat findByNameAndStatusAndCreatedAtBetween... qilib yozish yomonlashadi.

Shu joyda advanced Spring Data kerak bo‘ladi.


2. Oddiy derived query muammosi

List<User> findByNameAndStatusAndCityAndCreatedAtBetween(
        String name,
        UserStatus status,
        String city,
        LocalDateTime from,
        LocalDateTime to
);

Boshida ishlaydi. Keyin requirement o‘zgaradi:

name optional
status optional
city optional
date optional
role optional

Endi muammo:

findByName(...)
findByStatus(...)
findByCity(...)
findByNameAndStatus(...)
findByNameAndCity(...)
findByStatusAndCity(...)
findByNameAndStatusAndCity(...)

Bu maintain qilib bo‘lmaydigan kodga aylanadi.

Yechimlar:

  • Specification

  • Criteria API

  • QueryDSL

  • custom repository

  • JPQL dynamic query


3. Specification nima?

Specification - dynamic query yozish usuli.

Oddiy ma’noda:

Specification - query shartlarini alohida-alohida qilib yig‘ish imkonini beradi.

Masalan:

status = ACTIVE
AND city = Tashkent
AND age > 18

Bularni alohida methodlarga ajratib, keraklisini qo‘shib ketish mumkin.


4. Specification ishlatish uchun repository

Entity:

@Entity
public class User {

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

    private String fullName;

    private String phone;

    @Enumerated(EnumType.STRING)
    private UserStatus status;

    private String city;

    private LocalDateTime createdAt;
}

Enum:

public enum UserStatus {
    ACTIVE,
    BLOCKED,
    DELETED
}

Repository:

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

Muhim joy:

JpaSpecificationExecutor<User>

Shu interface Specification bilan query qilish imkonini beradi.


5. Oddiy Specification yozish

public class UserSpecifications {

    public static Specification<User> hasStatus(UserStatus status) {
        return (root, query, criteriaBuilder) ->
                criteriaBuilder.equal(root.get("status"), status);
    }

    public static Specification<User> hasCity(String city) {
        return (root, query, criteriaBuilder) ->
                criteriaBuilder.equal(root.get("city"), city);
    }

    public static Specification<User> nameContains(String name) {
        return (root, query, criteriaBuilder) ->
                criteriaBuilder.like(
                        criteriaBuilder.lower(root.get("fullName")),
                        "%" + name.toLowerCase() + "%"
                );
    }
}

Bu yerda:

Qism

Ma’nosi

root

Entity jadvali, masalan User

query

Query obyektining o‘zi

criteriaBuilder

SQL shartlarini yasash uchun builder

root.get("status")

status column

criteriaBuilder.equal(...)

where status = ?


6. Specification ishlatish

Specification<User> spec = Specification
        .where(UserSpecifications.hasStatus(UserStatus.ACTIVE))
        .and(UserSpecifications.hasCity("Tashkent"));

Repository:

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

Taxminiy SQL:

select *
from users
where status = 'ACTIVE'
  and city = 'Tashkent';

7. Optional filterlar bilan real search

Request DTO:

public record UserSearchRequest(
        String name,
        UserStatus status,
        String city,
        LocalDateTime from,
        LocalDateTime to
) {
}

Specification builder:

public class UserSpecifications {

    public static Specification<User> filter(UserSearchRequest request) {
        Specification<User> spec = Specification.where(null);

        if (request.name() != null && !request.name().isBlank()) {
            spec = spec.and(nameContains(request.name()));
        }

        if (request.status() != null) {
            spec = spec.and(hasStatus(request.status()));
        }

        if (request.city() != null && !request.city().isBlank()) {
            spec = spec.and(hasCity(request.city()));
        }

        if (request.from() != null) {
            spec = spec.and(createdAfter(request.from()));
        }

        if (request.to() != null) {
            spec = spec.and(createdBefore(request.to()));
        }

        return spec;
    }

    private static Specification<User> nameContains(String name) {
        return (root, query, cb) ->
                cb.like(
                        cb.lower(root.get("fullName")),
                        "%" + name.toLowerCase() + "%"
                );
    }

    private static Specification<User> hasStatus(UserStatus status) {
        return (root, query, cb) ->
                cb.equal(root.get("status"), status);
    }

    private static Specification<User> hasCity(String city) {
        return (root, query, cb) ->
                cb.equal(root.get("city"), city);
    }

    private static Specification<User> createdAfter(LocalDateTime from) {
        return (root, query, cb) ->
                cb.greaterThanOrEqualTo(root.get("createdAt"), from);
    }

    private static Specification<User> createdBefore(LocalDateTime to) {
        return (root, query, cb) ->
                cb.lessThanOrEqualTo(root.get("createdAt"), to);
    }
}

Service:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional(readOnly = true)
    public Page<User> search(UserSearchRequest request, Pageable pageable) {
        Specification<User> spec = UserSpecifications.filter(request);
        return userRepository.findAll(spec, pageable);
    }
}

Controller:

@GetMapping("/users")
public Page<User> search(
        UserSearchRequest request,
        Pageable pageable
) {
    return userService.search(request, pageable);
}

8. Specification + pagination + sorting

Spring Data buni tayyor beradi:

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

Page<User> page = userRepository.findAll(spec, pageable);

REST API’da:

GET /users?status=ACTIVE&city=Tashkent&page=0&size=20&sort=createdAt,desc

Bu real projectlarda juda ko‘p ishlatiladi.


9. Specification qachon yaxshi?

Specification yaxshi:

Holat

Sabab

Ko‘p optional filterlar bor

Queryni dynamic yig‘ish oson

Admin panel search

Filterlar soni ko‘p bo‘ladi

CRM/ERP sistemalar

Search murakkab bo‘ladi

Pagination kerak

findAll(spec, pageable) tayyor

Reusable filterlar kerak

Har bir shart method bo‘lib turadi

Lekin juda murakkab report querylar uchun Specification noqulay bo‘lishi mumkin. Bunda native SQL, QueryDSL yoki custom repository yaxshiroq.


10. Criteria API nima?

Specification ichida ishlatilayotgan narsa aslida JPA Criteria API.

Criteria API - SQL/JPQLni string bilan emas, Java obyektlari bilan qurish usuli.

Masalan JPQL:

@Query("select u from User u where u.status = :status")
List<User> findActive(UserStatus status);

Criteria API bilan:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);

query.select(root)
        .where(cb.equal(root.get("status"), UserStatus.ACTIVE));

List<User> users = entityManager
        .createQuery(query)
        .getResultList();

Criteria API juda verbose. Shu sabab ko‘pincha uni to‘g‘ridan-to‘g‘ri emas, Specification orqali ishlatamiz.


11. Projection nima?

Projection - entityning hammasini emas, faqat kerakli fieldlarni olish.

Masalan User entity katta bo‘lishi mumkin:

@Entity
public class User {
    private Long id;
    private String fullName;
    private String phone;
    private String passwordHash;
    private String passportNumber;
    private LocalDateTime createdAt;
}

API’da esa faqat shu kerak:

{
  "id": 1,
  "fullName": "Ali Valiyev"
}

Entityni to‘liq olib, keyin DTOga map qilish har doim ham optimal emas. Projection yordamida DBdan kerakli columnlargina olinadi.


12. Interface-based projection

Projection interface:

public interface UserShortView {

    Long getId();

    String getFullName();
}

Repository:

public interface UserRepository extends JpaRepository<User, Long> {

    List<UserShortView> findByStatus(UserStatus status);
}

Ishlatish:

List<UserShortView> users =
        userRepository.findByStatus(UserStatus.ACTIVE);

Natija faqat kerakli fieldlarni beradi.


13. DTO projection

DTO:

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

Repository:

@Query("""
    select new com.example.dto.UserShortDto(
        u.id,
        u.fullName
    )
    from User u
    where u.status = :status
""")
List<UserShortDto> findShortUsersByStatus(UserStatus status);

Bu aniqroq va API response uchun qulay.


14. Projection qachon kerak?

Projection ishlating:

Holat

Sabab

List endpoint

Hammasi kerak emas

Mobile API

Response yengil bo‘ladi

Admin table

Faqat table columnlar kerak

Sensitive fieldlar bor

Password/passport chiqib ketmaydi

Performance kerak

Kamroq data olinadi

Yomon amaliyot:

@GetMapping("/users")
public List<User> getUsers() {
    return userRepository.findAll();
}

Nega yomon?

  • password hash chiqib ketishi mumkin;

  • entity relationlar JSONda loop bo‘lishi mumkin;

  • lazy loading muammosi chiqadi;

  • response og‘irlashadi;

  • API entityga bog‘lanib qoladi.

Yaxshi:

@GetMapping("/users")
public Page<UserShortDto> getUsers(Pageable pageable) {
    return userService.getUsers(pageable);
}

15. Dynamic projection

Spring Data’da method parametr orqali projection turini tanlash ham mumkin:

public interface UserRepository extends JpaRepository<User, Long> {

    <T> List<T> findByStatus(UserStatus status, Class<T> type);
}

Projectionlar:

public interface UserShortView {
    Long getId();
    String getFullName();
}
public interface UserPhoneView {
    Long getId();
    String getPhone();
}

Ishlatish:

List<UserShortView> shortUsers =
        userRepository.findByStatus(
                UserStatus.ACTIVE,
                UserShortView.class
        );

List<UserPhoneView> phoneUsers =
        userRepository.findByStatus(
                UserStatus.ACTIVE,
                UserPhoneView.class
        );

Bu kuchli, lekin haddan tashqari ishlatilsa kod tushunarsizlashadi.


16. Auditing nima?

Auditing - entity qachon yaratilgan, qachon o‘zgargan, kim yaratgan, kim o‘zgartirganini avtomatik saqlash.

Ko‘p entitylarda bular bo‘ladi:

createdAt
updatedAt
createdBy
updatedBy

Har safar qo‘lda yozish yomon:

user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());

Spring Data Auditing buni avtomatik qiladi.


17. Auditing yoqish

Main class yoki config:

@EnableJpaAuditing
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

Base entity:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

Entity:

@Entity
public class Product extends BaseEntity {

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

    private String name;
}

Endi Product save bo‘lganda createdAt, updatedAt avtomatik to‘ladi.


18. createdBy va updatedBy

Base entity:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    @CreatedBy
    @Column(updatable = false)
    private Long createdBy;

    @LastModifiedBy
    private Long updatedBy;
}

AuditorAware:

@Component
public class SpringSecurityAuditorAware
        implements AuditorAware<Long> {

    @Override
    public Optional<Long> getCurrentAuditor() {
        Authentication authentication =
                SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null ||
                !authentication.isAuthenticated()) {
            return Optional.empty();
        }

        CustomUserDetails userDetails =
                (CustomUserDetails) authentication.getPrincipal();

        return Optional.of(userDetails.getId());
    }
}

Bu yerda current user ID security contextdan olinadi.


19. Auditing qachon foydali?

Auditing kerak:

Holat

Sabab

Admin panel

Kim o‘zgartirganini ko‘rish

CRM/ERP

Data history muhim

Debugging

Qachon o‘zgarganini bilish

Security

Sensitive actionlarni kuzatish

Reporting

Created date bo‘yicha filter

Lekin auditing to‘liq history emas. U faqat oxirgi holatni saqlaydi:

createdAt = birinchi yaratilgan vaqt
updatedAt = oxirgi o‘zgargan vaqt

Agar har bir o‘zgarish tarixini saqlash kerak bo‘lsa, Envers yoki audit log kerak.


20. Optimistic locking nima?

Optimistic locking - parallel update muammolarini hal qilish usuli.

Masalan ikkita admin bitta productni ochdi:

Admin A: product price = 100 ko‘rdi
Admin B: product price = 100 ko‘rdi

Admin A: price = 120 qilib saqladi
Admin B: price = 90 qilib saqladi

Natijada Admin A o‘zgarishi yo‘qolib ketishi mumkin. Bu lost update.

Optimistic locking buni oldini oladi.


21. @Version

Entityga version qo‘shamiz:

@Entity
public class Product {

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

    private String name;

    private BigDecimal price;

    @Version
    private Long version;
}

Endi Hibernate update qilganda taxminan shunday SQL yozadi:

update product
set name = ?, price = ?, version = version + 1
where id = ? and version = ?;

Agar boshqa transaction oldin update qilib versionni oshirib yuborgan bo‘lsa, update 0 rowga ta’sir qiladi va exception chiqadi.


22. Optimistic locking oqimi

Product version = 1

Admin A productni oldi: version = 1
Admin B productni oldi: version = 1

Admin A save qildi:
where id = 1 and version = 1
success, version = 2

Admin B save qildi:
where id = 1 and version = 1
0 rows updated -> OptimisticLockException

Bu to‘g‘ri xatti-harakat. Sistemada "oxirgi yozgan yutadi" emas, "conflict bor" deyiladi.


23. Optimistic lock exceptionni handle qilish

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ObjectOptimisticLockingFailureException.class)
    public ResponseEntity<?> handleOptimisticLock(
            ObjectOptimisticLockingFailureException ex
    ) {
        return ResponseEntity.status(HttpStatus.CONFLICT)
                .body(Map.of(
                        "error", "DATA_CONFLICT",
                        "message", "Data was changed by another user. Please reload and try again."
                ));
    }
}

HTTP status:

409 Conflict

Bu API uchun to‘g‘riroq.


24. Optimistic vs pessimistic locking

Lock turi

Qanday ishlaydi?

Qachon yaxshi?

Optimistic

Version tekshiradi

Conflict kam bo‘lsa

Pessimistic

Row lock qiladi

Conflict ko‘p bo‘lsa

Atomic update

Direct update query

Stock/balance kabi joylarda

Optimistic locking odatda yaxshi:

  • admin panel;

  • product edit;

  • user profile edit;

  • document edit;

  • kam conflictli CRUD.

Stock kamaytirish, balance update qilish kabi joylarda esa atomic update yoki pessimistic lock yaxshiroq bo‘lishi mumkin.


25. Spring Data Envers nima?

Hibernate Envers - entity o‘zgarish tarixini saqlash mexanizmi.

Auditing faqat buni beradi:

Product:
createdAt
updatedAt

Envers esa har bir o‘zgarish tarixini saqlaydi:

Product version 1: price = 100
Product version 2: price = 120
Product version 3: price = 90

Bu quyidagilar uchun kerak:

  • audit history;

  • kim qachon nima o‘zgartirdi;

  • oldingi holatni ko‘rish;

  • regulatory requirement;

  • admin action tracking.


26. Envers dependency

Maven:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-envers</artifactId>
</dependency>

Ko‘pincha Hibernate Envers ham kerak bo‘ladi:

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-envers</artifactId>
</dependency>

27. Entityda @Audited

@Entity
@Audited
public class Product {

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

    private String name;

    private BigDecimal price;
}

Endi Envers audit table yaratadi:

product
product_aud
revinfo

product_aud ichida productning eski holatlari saqlanadi.


28. Envers repository

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

Ishlatish:

Revisions<Integer, Product> revisions =
        productRepository.findRevisions(productId);

Oxirgi revision:

Revision<Integer, Product> lastRevision =
        productRepository.findLastChangeRevision(productId)
                .orElseThrow();

29. Envers qachon kerak emas?

Enversni hamma entityga qo‘shish kerak emas.

Kerak bo‘lmaydi:

  • lookup table;

  • juda katta log entitylar;

  • tez-tez o‘zgaradigan high-volume data;

  • vaqtinchalik data;

  • cache-like table.

Sabab:

  • audit table kattalashadi;

  • write overhead oshadi;

  • storage ko‘payadi;

  • querylar murakkablashadi.

Faqat muhim entitylarga qo‘shing:

User
Role
Product
Contract
Order status
Payment setting
Tariff

30. QueryDSL nima?

QueryDSL - type-safe query yozish uchun kutubxona.

Specificationda biz string ishlatdik:

root.get("fullName")

Agar field nomi o‘zgarsa, compile-time error bermaydi. Runtime’da xato chiqadi.

QueryDSLda esa generated classlar ishlatiladi:

QUser user = QUser.user;

queryFactory
        .selectFrom(user)
        .where(user.fullName.containsIgnoreCase("ali"))
        .fetch();

Agar fullName field o‘chirilsa yoki nomi o‘zgarsa, compile error chiqadi. Bu katta projectlarda kuchli afzallik.


31. QueryDSL qachon yaxshi?

QueryDSL yaxshi:

Holat

Sabab

Murakkab dynamic query

Readable bo‘ladi

Compile-time safety kerak

Field nomi xatosi kamayadi

Joinlar ko‘p

JPQL stringdan tozaroq

Report querylar

Select/projection oson

Katta project

Refactoring xavfsizroq

Kamchiligi:

  • setup murakkabroq;

  • Q-class generation kerak;

  • jamoa o‘rganishi kerak;

  • oddiy CRUD uchun ortiqcha.


32. QueryDSL oddiy misol

QueryDSL generated class:

QUser user = QUser.user;

Custom repository:

@Repository
public class UserQueryRepository {

    private final JPAQueryFactory queryFactory;

    public UserQueryRepository(EntityManager entityManager) {
        this.queryFactory = new JPAQueryFactory(entityManager);
    }

    public List<User> search(String name, UserStatus status) {
        QUser user = QUser.user;

        BooleanBuilder builder = new BooleanBuilder();

        if (name != null && !name.isBlank()) {
            builder.and(user.fullName.containsIgnoreCase(name));
        }

        if (status != null) {
            builder.and(user.status.eq(status));
        }

        return queryFactory
                .selectFrom(user)
                .where(builder)
                .orderBy(user.createdAt.desc())
                .fetch();
    }
}

Bu Specificationga qaraganda o‘qilishi osonroq bo‘lishi mumkin.


33. QueryDSL DTO projection

DTO:

public record UserListDto(
        Long id,
        String fullName,
        String phone
) {
}

Query:

public List<UserListDto> findUsers() {
    QUser user = QUser.user;

    return queryFactory
            .select(Projections.constructor(
                    UserListDto.class,
                    user.id,
                    user.fullName,
                    user.phone
            ))
            .from(user)
            .where(user.status.eq(UserStatus.ACTIVE))
            .fetch();
}

Bu report/list endpointlar uchun qulay.


34. Specification vs QueryDSL

Mezон

Specification

QueryDSL

Setup

Oson

Murakkabroq

Type safety

Pastroq, string fieldlar bor

Yuqori

Dynamic filter

Yaxshi

Juda yaxshi

Readability

O‘rtacha

Ko‘pincha yaxshiroq

Kichik project

Yetarli

Ortiqcha bo‘lishi mumkin

Katta project

Ba’zan noqulay

Kuchliroq

Amaliy tavsiya:

Oddiy admin filterlar -> Specification
Murakkab query/report/search -> QueryDSL
Juda performance-sensitive query -> native SQL yoki jOOQ

35. N+1 muammo

Spring Data advanced mavzusida buni ham tushunish shart.

Entity:

@Entity
public class Order {

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

Service:

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

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

Muammo:

1 query: ordersni olish
N query: har bir order uchun userni olish

Agar 100 ta order bo‘lsa:

1 + 100 query

Bu N+1 problem.


36. Fetch join

Repository:

@Query("""
    select o
    from Order o
    join fetch o.user
    where o.status = :status
""")
List<Order> findByStatusWithUser(OrderStatus status);

Bu order va userni bitta queryda olib keladi.

Taxminiy SQL:

select o.*, u.*
from orders o
join users u on u.id = o.user_id
where o.status = ?;

37. EntityGraph

@EntityGraph ham relationlarni oldindan yuklash uchun ishlatiladi.

@EntityGraph(attributePaths = {"user", "items"})
List<Order> findByStatus(OrderStatus status);

Bu method chaqirilganda user va items oldindan yuklanadi.

Lekin ehtiyot bo‘lish kerak. Ko‘p collectionlarni bir vaqtda fetch qilish katta result set va duplicate row muammolarini chiqarishi mumkin.


38. Bulk update

Oddiy JPA entity update:

@Transactional
public void blockUsers() {
    List<User> users = userRepository.findByStatus(UserStatus.INACTIVE);

    for (User user : users) {
        user.setStatus(UserStatus.BLOCKED);
    }
}

Agar 100 ming user bo‘lsa, bu og‘ir.

Bulk update:

@Modifying
@Query("""
    update User u
    set u.status = :newStatus
    where u.status = :oldStatus
""")
int updateStatus(
        UserStatus oldStatus,
        UserStatus newStatus
);

Service:

@Transactional
public int blockInactiveUsers() {
    return userRepository.updateStatus(
            UserStatus.INACTIVE,
            UserStatus.BLOCKED
    );
}

Muhim: bulk update persistence contextni avtomatik yangilamasligi mumkin. Shu sabab bulk operationdan keyin entity manager clear kerak bo‘lishi mumkin.

@Modifying(clearAutomatically = true, flushAutomatically = true)

39. @Modifying

@Query default holatda select query deb qaraladi.

Update/delete uchun:

@Modifying
@Query("delete from User u where u.status = :status")
int deleteByStatus(UserStatus status);

Va service method transaction ichida bo‘lishi kerak:

@Transactional
public int deleteBlockedUsers() {
    return userRepository.deleteByStatus(UserStatus.BLOCKED);
}

40. Advanced Spring Data best practice

1. Entityni API response sifatida qaytarmang

Yomon:

return userRepository.findAll();

Yaxshi:

return userRepository.findAllProjectedBy();

yoki DTO mapping.


2. Optional filterlarda Specification ishlating

Yomon:

findByNameAndStatusAndCityAndCreatedAtBetween(...)

Yaxshi:

userRepository.findAll(UserSpecifications.filter(request), pageable);

3. List endpointlarda projection ishlating

Entity katta bo‘lsa, faqat kerakli fieldlarni oling.


4. Audit fieldlarni qo‘lda to‘ldirmang

@CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy ishlating.


5. Parallel update uchun @Version qo‘shing

Muhim entitylarda lost update oldini oladi.


6. N+1 muammosini testda tekshiring

Ayniqsa:

order.getUser()
order.getItems()
user.getRoles()

kabi relationlarda.


7. Bulk operationlarda persistence contextga ehtiyot bo‘ling

Bulk query DBni o‘zgartiradi, lekin memorydagi entitylar eski holatda qolishi mumkin.


41. Real project uchun pattern

Masalan admin panelda user search endpoint:

GET /admin/users?name=ali&status=ACTIVE&city=Tashkent&page=0&size=20

Yaxshi arxitektura:

Controller
  ↓
UserSearchRequest
  ↓
Service
  ↓
Specification
  ↓
Repository
  ↓
Page<UserListDto>

Service:

@Transactional(readOnly = true)
public Page<UserListDto> search(UserSearchRequest request, Pageable pageable) {
    Specification<User> spec = UserSpecifications.filter(request);

    return userRepository.findAll(spec, pageable)
            .map(userMapper::toListDto);
}

Bu clean va maintainable.

Agar performance kerak bo‘lsa, projectionni repository darajasida qilish mumkin.


42. Interview savollar

Savol 1: Specification nima?

Javob:

Specification - Spring Data JPA’da dynamic query shartlarini composable qilib yozish usuli. U Criteria API ustiga qurilgan va optional filterlar ko‘p bo‘lgan search endpointlarda foydali.


Savol 2: Projection nima uchun kerak?

Javob:

Projection entityning hammasini emas, faqat kerakli fieldlarni olish uchun ishlatiladi. Bu response hajmini kamaytiradi, sensitive fieldlar chiqib ketishining oldini oladi va performancega yordam beradi.


Savol 3: Auditing nima?

Javob:

Auditing entity yaratilgan va o‘zgartirilgan vaqtni, shuningdek kim yaratgani yoki o‘zgartirganini avtomatik saqlash mexanizmi. Spring Data’da @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy ishlatiladi.


Savol 4: Optimistic locking qanday ishlaydi?

Javob:

Entityga @Version field qo‘shiladi. Update paytida Hibernate eski versionni tekshiradi. Agar boshqa transaction oldin update qilgan bo‘lsa, version mos kelmaydi va optimistic lock exception chiqadi.


Savol 5: Envers nima?

Javob:

Hibernate Envers entity o‘zgarish tarixini saqlaydi. Oddiy auditing oxirgi holatni saqlasa, Envers har bir revisionni alohida audit tablelarda saqlaydi.


Savol 6: QueryDSL Specificationdan nimasi bilan farq qiladi?

Javob:

Specification Criteria API asosida ishlaydi va ba’zan string field nomlariga tayanadi. QueryDSL esa generated Q-classlar orqali type-safe query yozadi, shuning uchun refactoringda xatolar compile time’da chiqadi.


Savol 7: N+1 muammo nima?

Javob:

N+1 - avval asosiy entitylar bitta query bilan olinib, keyin har bir entity relationi uchun alohida query ketishi. Masalan 100 order va har birining userini olish 1 + 100 queryga aylanadi. Fetch join yoki EntityGraph bilan hal qilinadi.


43. Qisqa xulosa

Spring Data advanced - katta projectlarda data access qatlamini professional boshqarish uchun kerak.

Mavzu

Qachon kerak

Specification

Optional filterlar ko‘p bo‘lsa

Criteria API

Dynamic queryni pastroq darajada boshqarish kerak bo‘lsa

Projection

Faqat kerakli fieldlarni olish uchun

Auditing

createdAt/updatedAt/createdBy/updatedBy uchun

Optimistic locking

Parallel update conflictlarini oldini olish uchun

Envers

Entity history kerak bo‘lsa

QueryDSL

Type-safe va murakkab querylar uchun

Fetch join / EntityGraph

N+1 muammosini hal qilish uchun

Bulk update

Katta hajmdagi update/delete uchun

Eng muhim fikr:

Spring Data advanced - querylarni ko‘paytirish emas, balki data accessni nazoratli, xavfsiz va performancega mos qilishdir.