Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/hodadako step2 #10

Open
wants to merge 21 commits into
base: hodadako
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8f54171
refactor : DTO 세분화
hodadako Nov 11, 2024
f089469
feat : 송금 통계 테이블 추가
hodadako Nov 11, 2024
cdaa674
feat : 일 단위 전체 송금 금액 과 누적 송금 금액 조회를 위한 로직 추가
hodadako Nov 11, 2024
1218dd7
feat : 일 단위 통계를 집계하기 위한 저장 로직 구현
hodadako Nov 11, 2024
86663df
feat : 거래 테이블 추가
hodadako Nov 11, 2024
706ca99
feat : 통계 집계를 위한 거래 테이블 조회 기능 구현
hodadako Nov 11, 2024
859b1cf
fix : MySQL 잘못된 driver-class-name 수정
hodadako Nov 11, 2024
f3d8a77
fix : fetch join 수정
hodadako Nov 11, 2024
d068b63
feat : hibernate 설정 추가
hodadako Nov 11, 2024
5b1bdd6
refactor : 날짜 데이터 타입 수정
hodadako Nov 11, 2024
247f630
feat : 단일 송금 통계 조회 기능 추가
hodadako Nov 11, 2024
e7e28b0
feat : 통계 기능 구현
hodadako Nov 11, 2024
cd2bba5
feat : 통계에 날짜 범위를 받기 위한 DTO 추가
hodadako Nov 11, 2024
6e69f79
style : class 이름 명시적으로 변경
hodadako Nov 11, 2024
0c95cb2
feat : presentation layer 추가
hodadako Nov 11, 2024
62f666b
fix : Transaction 테이블과 일치하도록 수정
hodadako Nov 11, 2024
b1835c2
fix : Transaction 테이블 변경에 따른 통계 테이블 변경
hodadako Nov 11, 2024
ae2ce83
fix : TransactionRepository에서 바로 통계를 만들어서 전달하도록 로직 변경
hodadako Nov 11, 2024
1df5d6f
refactor : TransferStatistics 데이터 접근 레이어 변경에 따른 서비스 리팩토링
hodadako Nov 11, 2024
cac2551
fix : Transaction 컬럼 수정
hodadako Nov 11, 2024
d6bed25
fix : Transaction 컬럼 수정
hodadako Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies {
implementation 'org.springframework.retry:spring-retry'
implementation 'org.apache.commons:commons-lang3:3.13.0'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'com.mysql:mysql-connector-j'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;

public class EmailRecordRequest {
public record Save(
@NotNull
@Positive
Long memberId,
@NotBlank
String content,
@NotBlank
@Size(max = 50)
String subject
) {
public EmailRecord toEntity(Member member) {
return new EmailRecord(member, this.content, this.subject);
}
public record SaveEmailRecordRequest(
@NotNull
@Positive
Long memberId,
@NotBlank
String content,
@NotBlank
@Size(max = 50)
String subject
) {
public EmailRecord toEntity(Member member) {
return new EmailRecord(member, this.content, this.subject);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.net.URI;

import org.c4marathon.assignment.domain.email_record.dto.EmailRecordRequest;
import org.c4marathon.assignment.domain.email_record.dto.SaveEmailRecordRequest;
import org.c4marathon.assignment.domain.email_record.entity.EmailRecord;
import org.c4marathon.assignment.domain.email_record.service.EmailRecordService;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -25,7 +25,7 @@ public class EmailRecordController {
private String baseUrl;

@PostMapping("/v1/email-records")
public ResponseEntity<EmailRecord> saveEmailRecord(@Valid EmailRecordRequest.Save request) {
public ResponseEntity<EmailRecord> saveEmailRecord(@Valid SaveEmailRecordRequest request) {
EmailRecord saved = emailRecordService.save(request);
URI uri = UriComponentsBuilder.fromUriString(baseUrl + saved.getId())
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface EmailRecordRepository extends JpaRepository<EmailRecord, Long>
"""
SELECT new org.c4marathon.assignment.domain.email_record.query.EmailRecordQueryResult(er.id, er.member.email, er.subject, er.content)
FROM EmailRecord er
LEFT JOIN FETCH er.member
LEFT JOIN er.member
WHERE er.emailStatus =:status
ORDER BY er.id ASC
""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

import static org.c4marathon.assignment.common.configuration.AsyncConfig.*;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.c4marathon.assignment.domain.email_record.dto.EmailRecordRequest;
import org.c4marathon.assignment.domain.email_record.dto.SaveEmailRecordRequest;
import org.c4marathon.assignment.domain.email_record.entity.EmailRecord;
import org.c4marathon.assignment.domain.email_record.event.EmailRecordEvent;
import org.c4marathon.assignment.domain.email_record.query.EmailRecordQueryResult;
Expand All @@ -30,7 +26,7 @@ public class EmailRecordService {
private final ApplicationEventPublisher eventPublisher;

@Transactional
public EmailRecord save(EmailRecordRequest.Save request) {
public EmailRecord save(SaveEmailRecordRequest request) {
var member = memberReader.find(request.memberId());
return emailRecordStore.store(request.toEntity(member));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.c4marathon.assignment.domain.transaction.entity;

import java.time.LocalDateTime;
import java.util.Objects;

import org.c4marathon.assignment.common.entity.BaseEntity;
import org.hibernate.annotations.ColumnDefault;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "transaction", indexes = {})
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "transaction_id") // 여기서 컬럼명을 "transaction_id"로 변경
private Integer id;

@Column(name = "sender_account", columnDefinition = "varchar(20)", nullable = false)
private String senderAccount;

@Column(name = "receiver_account", columnDefinition = "varchar(20)", nullable = false)
private String receiverAccount;

@Column(name = "sender_swift_code", columnDefinition = "varchar(11)", nullable = false)
private String senderSwiftCode;

@Column(name = "receiver_swift_code", columnDefinition = "varchar(11)", nullable = false)
private String receiverSwiftCode;

@Column(name = "sender_name", columnDefinition = "varchar(30)", nullable = false)
private String senderName;

@Column(name = "receiver_name", columnDefinition = "varchar(30)", nullable = false)
private String receiverName;

@Column(name = "amount", nullable = false)
private Long amount;

@Column(name = "memo", columnDefinition = "varchar(200)")
private String memo;

@ColumnDefault("CURRENT_TIMESTAMP")
@Column(name = "transaction_date", nullable = false)
private LocalDateTime transactionDate;

@Override
public boolean equals(Object o) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전반적으로 equalshashCode 를 정의하고 있는데요. 혹시 해당 데이터가 HashMap 에 들어갈 것을 상정하셔서 그런걸까요?

if (o instanceof Transaction t) {
return this.getId() == t.getId();
}
return false;
}

@Override
public int hashCode() {
return Objects.hash(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.c4marathon.assignment.domain.transaction.entity;

public enum TransactionType {
TRANSFER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.c4marathon.assignment.domain.transaction.repository;

import java.time.LocalDate;
import java.util.stream.Stream;

import org.c4marathon.assignment.domain.transfer_statistics.entity.TransferStatistics;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class TransactionReader {
private final TransactionRepository transactionRepository;

public Stream<TransferStatistics> findTransferStatisticsBetweeen(LocalDate from, LocalDate to) {
return transactionRepository.findTransactionBetween(from, to);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.c4marathon.assignment.domain.transaction.repository;

import java.time.LocalDate;
import java.util.stream.Stream;

import org.c4marathon.assignment.domain.transaction.entity.Transaction;
import org.c4marathon.assignment.domain.transfer_statistics.entity.TransferStatistics;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface TransactionRepository extends JpaRepository<Transaction, Long> {
@Query(
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 해당 쿼리의 실행계획을 확인하셨을까요?
(해당 테이블의 데이터가 많은 것 같지만, 실제로는 그렇게 많은 것도 아닙니다.)

SELECT new TransferStatistics(SUM(t.amount),
SUM(SUM(t.amount)) OVER (ORDER BY DATE(t.transactionDate)), t.transactionDate)
FROM Transaction t
WHERE t.transactionDate BETWEEN :startDate AND :endDate
GROUP BY t.transactionDate
ORDER BY t.transactionDate DESC
""")
Stream<TransferStatistics> findTransactionBetween(@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.c4marathon.assignment.domain.transfer_statistics.dto;

import java.time.LocalDateTime;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.PastOrPresent;

public record AggregateTransferStatisticsRequest(
@NotBlank
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NotNull 을 원하신건가요?
@NotBlank 는 문자열에서 사용되는 어노테이션입니다.

@PastOrPresent
LocalDateTime targetDate
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.c4marathon.assignment.domain.transfer_statistics.dto;

import java.time.LocalDate;

import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Past;

public record GetAllTransferStatisticsRequest(
@NotBlank
@Past
LocalDate startDate,
@NotBlank
@Past
LocalDate endDate
) {
@AssertTrue(message = "시작 날짜는 종료 날짜보다 이전이어야 합니다.")
public boolean isStartDateBeforeEndDate() {
return startDate.isBefore(endDate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.c4marathon.assignment.domain.transfer_statistics.entity;

import java.time.LocalDateTime;
import java.util.Objects;

import org.c4marathon.assignment.common.entity.BaseEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "transfer_statistics_hodadako")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TransferStatistics extends BaseEntity {
@Column(name = "daily_total_amount", columnDefinition = "bigint")
private Long dailyTotalAmount;

@Column(name = "cumulative_total_amount", columnDefinition = "bigint")
private Long cumulativeTotalAmount;

@Column(name = "unit_date", columnDefinition = "datetime")
private LocalDateTime unitDate;

public TransferStatistics(Long dailyTotalAmount, Long cumulativeTotalAmount, LocalDateTime unitDate) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NoArgConstructor는 Lombok 으로 작성하셨는데, 정작 이건 Lombok을 안 쓰셨네요.
스타일은 통일해 주세요.

this.dailyTotalAmount = dailyTotalAmount;
this.cumulativeTotalAmount = cumulativeTotalAmount;
this.unitDate = unitDate;
}

@Override
public boolean equals(Object o) {
if (o instanceof TransferStatistics transferStatistics) {
return this.getId() == transferStatistics.getId();
}
return false;
}

@Override
public int hashCode() {
return Objects.hash(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.c4marathon.assignment.domain.transfer_statistics.presentation;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일반적으로 패키지명에 underscore를 삽입하는 것은 매우 권장되지 않습니다.

(ex1. https://google.github.io/styleguide/javaguide.html#s5.2.1-package-names)
(ex2. https://naver.github.io/hackday-conventions-java/#package-lowercase)


import java.time.LocalDate;
import java.util.List;

import org.c4marathon.assignment.domain.transfer_statistics.dto.AggregateTransferStatisticsRequest;
import org.c4marathon.assignment.domain.transfer_statistics.dto.GetAllTransferStatisticsRequest;
import org.c4marathon.assignment.domain.transfer_statistics.entity.TransferStatistics;
import org.c4marathon.assignment.domain.transfer_statistics.service.TransferStatisticsService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class TransferStatisticsController {
private final TransferStatisticsService transferStatisticsService;

@GetMapping("/v1/transfer-statistics")
public ResponseEntity<List<TransferStatistics>> getTransferStatisticsInBetween(
@RequestParam(name = "start-date") LocalDate startDate, @RequestParam(name = "end-date") LocalDate endDate) {
return ResponseEntity.ok(
transferStatisticsService.findAllByUnitDateBetween(
new GetAllTransferStatisticsRequest(startDate, endDate)));
}

@PostMapping("/v1/transfer-statistics")
public ResponseEntity<TransferStatistics> aggregateTransferStatistics(
@RequestBody AggregateTransferStatisticsRequest request
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request 객체에 Validation 어노테이션을 박았지만, 막상 여기서 @Valid를 안 씌우셔서 검증이 안 이뤄질 것 같네요.

) {
return ResponseEntity.ok(transferStatisticsService.getStatistics(request));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.c4marathon.assignment.domain.transfer_statistics.repository;

import java.time.LocalDate;
import java.util.stream.Stream;

import org.c4marathon.assignment.domain.transfer_statistics.entity.TransferStatistics;
import org.springframework.stereotype.Component;

import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class TransferStatisticsReader {
private final TransferStatisticsRepository transferStatisticsRepository;

public Stream<TransferStatistics> findAllByUnitDateBetween(LocalDate from, LocalDate to) {
return transferStatisticsRepository.findAllByUnitDateBetween(from, to);
}

public TransferStatistics findByUnitDate(LocalDate date) {
return transferStatisticsRepository.findByUnitDate(date)
.orElseThrow(() -> new EntityNotFoundException("해당 송금 통계는 존재하지 않습니다."));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 에러가 HTTP 500을 반환해야 하나요?

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.c4marathon.assignment.domain.transfer_statistics.repository;

import static org.hibernate.jpa.HibernateHints.*;

import java.time.LocalDate;
import java.util.Optional;
import java.util.stream.Stream;

import org.c4marathon.assignment.domain.transfer_statistics.entity.TransferStatistics;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.query.Param;

import jakarta.persistence.QueryHint;

public interface TransferStatisticsRepository extends JpaRepository<TransferStatistics, Long> {
@Query(
"""
SELECT ts FROM TransferStatistics ts
WHERE ts.unitDate between :startDate AND :endDate
ORDER BY ts.unitDate DESC
""")
@QueryHints(value = {
@QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MAX_VALUE),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String.valueOf(Integer.MAX_VALUE) 가 좀 더 자연스럽지 않을까요?

@QueryHint(name = HINT_CACHEABLE, value = "false"),
})
Stream<TransferStatistics> findAllByUnitDateBetween(@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);

Optional<TransferStatistics> findByUnitDate(LocalDate date);
}
Loading