본문 바로가기
Projects/청하-청년을 위한 커뮤니티 서비스

[청하] 35. 정책 조회수 기능 구현

by Lpromotion 2024. 10. 23.

“핫한 정책 조회” 기능을 구현하기 위해 먼저 정책에 대해 조회수 기능을 추가하기로 했다.

간단하게 구현하면 정책 테이블에 조회수 필드를 추가하면 되지만, 현재 이 프로젝트의 정책 테이블은 주기적으로 삭제되고 “청년 정책 Open API” 호출로 다시 새로운 데이터를 저장한다.

그래서 단순히 정책 테이블에 조회수 필드를 추가한다면, 정책 데이터가 리프레시 될 때 조회수도 초기화 될 것이다.

그래서 정책 조회수는 테이블을 따로 생성해서 관리하도록 결정했다.

 

1. 프로젝트 구조

src/main/java/com/example/withpeace/
│
├── domain/                   # 도메인 모델 (엔티티)
│   ├── YouthPolicy.java      # 청년 정책 엔티티
│   └── ViewPolicy.java       # 정책 조회 엔티티
│
├── repository/                       # 데이터 접근 계층
│   ├── YouthPolicyRepository.java    # 청년 정책 레포지토리
│   └── ViewPolicyRepository.java     # 정책 조회 레포지토리
│
├── controller/                 # 컨트롤러 계층
│   └── PolicyController.java   # 정책 관련 API 엔드포인트
│
└── service/                    # 비즈니스 로직 계층
    └── PolicyService.java      # 정책 관련 비즈니스 로직

 

2. 도메인 엔티티 구현

ViewPolicy

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicUpdate
@Table(name = "view_policies")
public class ViewPolicy {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false, unique = true)
    private Long id;

    @Column(name = "policy_id", nullable = false, unique = true)
    private String policyId; // 정책 ID

    @Column(name = "view_count", nullable = false)
    private int viewCount; // 조회수

    @Builder
    public ViewPolicy(String policyId, int viewCount) {
        this.policyId = policyId;
        this.viewCount = viewCount;
    }

}

 

3. 레포지토리 구현

ViewPolicyRepository

public interface ViewPolicyRepository extends JpaRepository<ViewPolicy, Long> {

    @Modifying
    @Query(value = "INSERT INTO view_policies (policy_id, view_count) VALUES (:policyId, 1) " +
            "ON DUPLICATE KEY UPDATE view_count = view_count + 1", nativeQuery = true)
    void incrementViewCount(String policyId);

}

`incrementViewCount` 메서드는 해당 정책의 조회수를 1 증가시킨다.

 

정책을 조회하게 되면, 정책 ID를 받아 해당 정책 ID가 “정책 조회수” 테이블에 존재하면 조회수를 1 증가시키고, 존재하지 않으면 새로 데이터를 삽입해야 한다.

이 두 가지를 한 번에 작동시킬 수 있는 쿼리를 사용하면 된다.

INSERT INTO 정책_조회수 (정책_id, 조회수) VALUES (:정책_id, 1)
ON DUPLICATE KEY UPDATE 조회수 = 조회수 + 1;

`ON DUPLICATE KEY UPDATE` 를 사용하면 INSERT 작업 시 UNIQUE 또는 PRIMARY KEY 제약 조건과 충돌이 발생할 경우 UPDATE를 수행한다.

  1. 해당 정책의 조회 기록이 없는 경우: INSERT 문이 실행되어 조회수 1로 새로운 레코드 생성
  2. 해당 정책의 조회 기록이 이미 있는 경우: UPDATE 문이 실행되어 기존 조회수에 1을 증가

여러 사용자가 동시에 조회하는 경우에도 조회수가 정확하게 증가한다. (동시성 처리)

 

4. 서비스 수정

YouthPolicyService

@Transactional
public PolicyDetailResponseDto getPolicyDetail(Long userId, String policyId) {
    User user = getUserById(userId);
    YouthPolicy policy = getPolicyById(policyId);
    boolean isFavorite = isFavoritePolicy(user, policy.getId());
    viewPolicyRepository.incrementViewCount(policyId); // 조회수 증가

    return PolicyDetailResponseDto.from(policy, isFavorite);
}

`getPolicyDetail` 는 “정책 상세 조회” 서비스 코드이다. “정책 리스트 조회”에서 한 정책을 선택하고 “상세 조회”를 요청할 때 조회수가 증가되도록 구현했다.

 

5. API 응답 예시

요청 URL

[GET] http://cheongha.site/api/v1/policies/R2024070424687

 

“정책 상세조회” 시 조회수를 증가시킬 수 있다.

 

6. 고려할 것

정책 데이터가 리프레시 되었을 때, 더 이상 존재하지 않는 정책에 대해 조회수 데이터를 어떻게 처리할지 생각해야 한다.

그래서 생각해본 방법으로는 주기적으로 정책 데이터를 리프레시 하는 것 처럼, 정책 조회수 데이터도 주기적으로 정책 조회수 테이블의 정책ID가 정책 테이블의 정책ID 중에 존재하는 지 검사하여 삭제하는 방법이다.

(스케줄러 또는 Spring Batch 사용을 해볼 수 있을 것 같다.)

팀원들에게 의견을 물어보고 처리할 것이다.

반응형

댓글