Angular Spring Boot ile Performanslı Sayfalama (Pagination) Sistemi - Adım Adım Rehber
Web uygulamalarında milyonlarca kayıt arasından veri çekerken tüm veriyi tek seferde yüklemek hem performans hem de kullanıcı deneyimi açısından kabul edilemez. Sayfalama (pagination) bu sorunu kökten çözer.
Bu yazıda, Spring Boot (backend) + Angular (frontend) kullanarak tamamen gerçek projeye uygulanabilir bir sayfalama sistemini adım adım birlikte oluşturacağız.
1. Backend: Spring Boot ile Sayfalama
a. Repository – Native SQL ile Sayfalama
Native query kullanarak tam kontrol sağlıyoruz. LIMIT ve OFFSET ile sayfalama yapıyoruz.
@Query(value = "SELECT p.id, " +
"p.short_content AS shortContent, " +
"p.title AS postTitle, " +
"c.title AS categoryTitle, " +
"pm.title AS postMetaTitle, " +
"p.created_at AS createdAt, " +
"c.id AS categoryId, " +
"p.link AS link " +
"FROM post p " +
"INNER JOIN post_category pc ON p.id = pc.post_id " +
"INNER JOIN category c ON c.id = pc.category_id " +
"INNER JOIN post_meta pm ON pm.post_id = p.id " +
"WHERE p.published = 1 " +
"ORDER BY p.id DESC " +
"LIMIT ?2 OFFSET ?1", nativeQuery = true)
List<Map<String, Object>> findLatestPosts(int offset, int limit);
b. Service Katmanı ve DTO
@Override
public Object[] getByTitlePost(String title, int page, int size) {
int offset = page * size;
Object[] result = new Object[2];
try {
List<Map<String, Object>> postListQuery = postRepository.findByTitle(title, offset, size);
List<PostCategoryMetaDTO> postList = postListQuery.stream()
.map(data -> new PostCategoryMetaDTO(
(BigInteger) data.get("id"),
(String) data.get("shortContent"),
(String) data.get("categoryTitle"),
(String) data.get("postMetaTitle"),
(String) data.get("postTitle"),
(Date) data.get("createdAt"),
(BigInteger) data.get("categoryId"),
(String) data.get("link")))
.collect(Collectors.toList());
result[0] = postList;
result[1] = offset; // Frontend'e toplam kayıt sayısı için başka yöntem de kullanılabilir
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
c. DTO (Lombok ile)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PostCategoryMetaDTO {
private BigInteger postId;
private String shortContent;
private String categoryTitle;
private String postMetaTitle;
private String postTitle;
private Date createdAt;
private BigInteger categoryId;
private String link;
}
d. Controller
@RestController
@RequestMapping("/api/posts")
@CrossOrigin(origins = "*")
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
@GetMapping("/getAllPost/{page}/{size}")
public ResponseEntity<Object[]> getAllPost(
@PathVariable int page,
@PathVariable int size) {
Object[] posts = postService.getByTitlePost(null, page, size);
return ResponseEntity.ok(posts);
}
}
2. Frontend: Angular ile Sayfalama
a. Post Service
@Injectable({
providedIn: 'root',
})
export class PostService {
private readonly API_URL = 'http://localhost:8080/api/posts';
constructor(private http: HttpClient) {}
getAllWithPagination(page: number, size: number): Observable<any> {
return this.http.get(`${this.API_URL}/getAllPost/${page}/${size}`);
}
}
b. Component (TypeScript)
import { Component, OnInit } from '@angular/core';
import { PostService } from './post.service';
@Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css'],
})
export class PostListComponent implements OnInit {
posts: any[] = [];
totalRecords: number = 0;
currentPage: number = 0;
rowsPerPage: number = 10;
constructor(private postService: PostService) {}
ngOnInit(): void {
this.loadPosts(this.currentPage, this.rowsPerPage);
}
loadPosts(page: number, size: number): void {
this.postService.getAllWithPagination(page, size).subscribe((data: any) => {
this.posts = data[0];
// Toplam kayıt sayısını ayrı bir endpoint ile almak daha iyi olur
// Burada örnek olduğu için offset döndürdük
});
}
onPageChange(event: any): void {
this.currentPage = event.page;
this.rowsPerPage = event.rows;
this.loadPosts(this.currentPage, this.rowsPerPage);
}
}
c. Template (HTML) + PrimeNG Paginator
<div class="posts-container">
<div *ngFor="let post of posts" class="post-card">
<h2>{{ post.postTitle }}</h2>
<p>{{ post.shortContent }}</p>
<span class="category">{{ post.categoryTitle }}</span>
</div>
</div>
<p-paginator
[rows]="rowsPerPage"
[totalRecords]="totalRecords"
(onPageChange)="onPageChange($event)">
</p-paginator>
İpucu: Daha İyi Performans İçin
- Backend’de
Page<T>vePageablekullanarak toplam kayıt sayısını tek sorguda alın. - Frontend’de PrimeNG, Angular Material veya ngx-pagination kullanabilirsiniz.
- Cache mekanizmaları (Redis, Caffeine) ekleyerek aynı sayfalar tekrar tekrar sorgulanmasın.
Sonuç
Artık Angular + Spring Boot projelerinizde milyonlarca kayıt bile olsa kullanıcılarınıza akıcı, hızlı ve profesyonel bir sayfalama deneyimi sunabilirsiniz.
Bu rehberi beğendiyseniz paylaşmayı ve takip etmeyi unutmayın!