2025년 6월 18일 수요일

Docker로 설치된 PostgreSQL에 CSV 파일을 insert하는 과정


1. Docker 컨테이너 실행:
- PostgreSQL 컨테이너가 실행 중이 아니라면, 다음 명령어로 컨테이너를 시작

```
docker start <container_name>
```

2. 컨테이너에 CSV 파일 복사:
- 호스트 머신에 있는 CSV 파일을 Docker 컨테이너 내부로 복사

```
docker cp <csv_file_path> <container_name>:/home
```

3. 컨테이너 내부로 접속:
- 다음 명령어를 사용하여 실행 중인 PostgreSQL 컨테이너 내부로 접속

```
docker exec -it <container_name> bash
```

4. 데이터베이스 접속:
- 컨테이너 내부에서 PostgreSQL 데이터베이스에 접속

```
psql -U <username> -d <database_name>
```

5. 테이블 생성:
- CSV 파일의 데이터를 저장할 테이블을 생성, 테이블 구조는 CSV 파일의 컬럼과 일치해야 함

```sql
CREATE TABLE <table_name> (
column1 datatype,
column2 datatype,
...
);
```

6. CSV 파일 import:
- `COPY` 명령어를 사용하여 CSV 파일의 데이터를 테이블에 insert

```sql
COPY <table_name> FROM '/home/file.csv' DELIMITER ',' CSV HEADER;
```

- `DELIMITER` CSV 파일에서 사용된 구분자를 지정
- `CSV HEADER` CSV 파일의 첫 번째 행이 헤더(컬럼명)임을 나타냄

7. 데이터 확인:
- `SELECT` 문을 사용하여 데이터가 성공적으로 insert되었는지 확인

```sql
SELECT * FROM <table_name> LIMIT 10;
```

>테이블 구조를 CSV 파일과 일치시키는 것이 중요

==데이터베이스 안에서 직접 파일 실행

```sql
\i /home/user/insert.sql
```


2025년 6월 16일 월요일

페이징 API와 스크롤 API

// common/ScrollRequest.java
public record ScrollRequest(
String nextToken, // 다음 페이지 토큰
int size // 요청 크기
) {
public static ScrollRequest of(String nextToken, int size) {
return new ScrollRequest(nextToken, size);
}
}

// common/PageRequest.java
public record PageRequest(
int page, // 페이지 번호
int size, // 페이지 크기
String sortBy, // 정렬 기준
String direction // 정렬 방향
) {
public static PageRequest of(int page, int size, String sortBy, String direction) {
return new PageRequest(page, size, sortBy, direction);
}

public org.springframework.data.domain.PageRequest toPageRequest() {
return org.springframework.data.domain.PageRequest.of(
page,
size,
direction.equalsIgnoreCase("DESC") ?
Sort.by(sortBy).descending() :
Sort.by(sortBy).ascending()
);
}
}

// repository/PostRepository.java
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// 페이징용 쿼리
@Query("""
select p
from Post p
join fetch p.writer
where p.deletedAt is null
""")
Page<Post> findAllPosts(Pageable pageable);

// 무한 스크롤용 쿼리
@Query("""
select p
from Post p
join fetch p.writer
where p.id < :lastPostId
and p.deletedAt is null
order by p.id desc
limit :size
""")
List<Post> findPostsForScroll(Long lastPostId, int size);
}

// service/PostService.java
@Service
@Transactional(readOnly = true)
public class PostService {
private final PostRepository postRepository;

// 페이징 방식
public PostListResponse findPostsByPaging(PageRequest request) {
Page<Post> posts = postRepository.findAllPosts(request.toPageRequest());
return PostListResponse.of(
posts.getContent().stream()
.map(PostResponse::from)
.toList(),
posts.getTotalElements(),
posts.getTotalPages(),
posts.hasNext()
);
}

// 무한 스크롤 방식
public ScrollPostResponse findPostsByScroll(ScrollRequest request) {
Long lastPostId = request.nextToken() != null ?
Long.parseLong(request.nextToken()) :
Long.MAX_VALUE;

List<Post> posts = postRepository.findPostsForScroll(lastPostId, request.size());

String nextToken = posts.size() == request.size() ?
String.valueOf(posts.get(posts.size() - 1).getId()) :
null;

return ScrollPostResponse.of(
posts.stream()
.map(PostResponse::from)
.toList(),
nextToken
);
}
}

// dto/PostListResponse.java
public record PostListResponse(
List<PostResponse> posts,
long totalElements,
int totalPages,
boolean hasNext
) {
public static PostListResponse of(
List<PostResponse> posts,
long totalElements,
int totalPages,
boolean hasNext
) {
return new PostListResponse(posts, totalElements, totalPages, hasNext);
}
}

// dto/ScrollPostResponse.java
public record ScrollPostResponse(
List<PostResponse> posts,
String nextToken
) {
public static ScrollPostResponse of(List<PostResponse> posts, String nextToken) {
return new ScrollPostResponse(posts, nextToken);
}
}


##### 구현된 기능 정리

1. 페이징 API
    - 전통적인 페이징 방식 구현
    - 전체 데이터 개수, 전체 페이지 수 제공
    - 정렬 기능 지원
    - 페이지 번호 기반 이동

2. 스크롤 API
    - 커서 기반 페이징 구현
    - nextToken을 사용한 다음 데이터 조회
    - 마지막 아이템 ID 기반 조회
    - 성능 최적화를 위한 LIMIT 사용

##### 주요 차이점

1. 데이터 접근 방식
    - 페이징: offset + limit 사용
    - 스크롤: 커서(lastPostId) 기반 조회
    
2. 반환 정보
    - 페이징: 전체 데이터 수, 전체 페이지 수 포함
    - 스크롤: 다음 조회를 위한 토큰만 포함

3. 성능 특성
    - 페이징: 데이터가 많을 경우 offset으로 인한 성능 저하 가능
    - 스크롤: 인덱스 기반 조회로 일관된 성능 보장

Docker로 설치된 PostgreSQL에 CSV 파일을 insert하는 과정

1. Docker 컨테이너 실행 : - PostgreSQL 컨테이너가 실행 중이 아니라면 , 다음 명령어로 컨테이너를 시작 ``` docker start <container_name> ``` 2. 컨테...