Skip to content

Commit 38595ed

Browse files
authored
[FEATURE/#38] CODEF 응답값 DB 저장 로직 추가 (#40)
## 🔗 Related Issue <!-- 이슈 번호를 작성하여 종료시켜주세요 --> - Closes #38 ## 📝 Summary <!-- 작업한 기능을 설명해주세요 --> - CODEF 자산 조회 및 DB 저장 - CODEF에서 조회한 보유계좌, 보유카드, 카드 승인내역, 은행 수시입출 거래내역을 DB에 저장하는 구조로 변경하였습니다. - 승인내역과 거래내역의 경우 데이터가 많으므로 비동기로 처리되도록 구현하였습니다. - 승인내역과 거래내역의 경우 Bulk Insert 최적화를 통해 수백 건 이상의 Insert가 개별 쿼리로 실행되는 문제를 방지했습니다. - 일부 계좌 유형(청약, 펀드 등)은 거래 내역 조회가 불가능한데, 이 경우에도 전체 동기화가 실패하지 않도록 예외 처리를 추가했습니다. - 카드 목록 조회 방식 변경 - 카드 목록을 매번 외부 API로 조회하고 있음을 확인하여 이를 DB 조회 방식으로 변경하였습니다. - API 명세서(Notion)와 실제 구현된 URL이 다른 부분을 수정했습니다 ## 🔄 Changes <!-- 구체적으로 어떤 파일/로직이 변경되었는지 체크해주세요 --> - [X] API 변경 (추가/수정) - [X] 데이터 및 도메인 변경 (DB, 비즈니스 로직) - [ ] 설정 또는 인프라 관련 변경 - [x] 리팩토링 ## 💬 Questions & Review Points <!-- 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 --> 거래내역 도메인 관련 작업을 진행하기 위해 우선 DB 저장이 완료되어야 하는 상황이어서, 빠른 개발을 위해 최소한의 에러 처리 및 로깅만 적용하였습니다. 상세한 에러 처리는 이후 단계에서 보완할 예정입니다. ## 📸 API Test Results <!-- API 테스트 스크린샷 첨부 --> - 카드 승인내역 및 은행 수시입출 거래내역 DB 저장 확인 <img width="1483" height="819" alt="image" src="https://github.com/user-attachments/assets/a283a5f4-3513-40e7-a54e-cb657ce0950d" /> <img width="1483" height="819" alt="image" src="https://github.com/user-attachments/assets/59e8ba7e-da62-4fc2-9dae-720d40c9ccf0" /> - 로그 성공 확인 <img width="1752" height="795" alt="image" src="https://github.com/user-attachments/assets/f1770582-bd8c-4401-b473-26af8dcc2a32" /> ## ✅ Checklist - [x] API 테스트 완료 - [x] 테스트 결과 사진 첨부 - [x] 빌드 성공 확인 (./gradlew build)
1 parent 4822864 commit 38595ed

28 files changed

Lines changed: 1097 additions & 50 deletions

src/main/java/org/umc/valuedi/domain/asset/controller/AssetController.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@ public class AssetController implements AssetControllerDocs {
2727

2828

2929
@GetMapping("/cards")
30-
public ApiResponse<List<CardResDTO.CardConnection>> getCards(
30+
public ApiResponse<CardResDTO.CardListDTO> getCards(
3131
// @CurrentMember Long memberId
3232
) {
3333
Long memberId = 1L; // 임시
34-
List<CardResDTO.CardConnection> cards = connectionQueryService.getConnectedCards(memberId);
35-
return ApiResponse.onSuccess(GeneralSuccessCode.OK, cards);
34+
return ApiResponse.onSuccess(GeneralSuccessCode.OK, assetQueryService.getAllCards(memberId));
3635
}
3736

38-
@GetMapping("/card-issuers")
37+
@GetMapping("/cardIssuers")
3938
public ApiResponse<List<CardResDTO.CardIssuerConnection>> getCardIssuers(
4039
// @CurrentMember Long memberId
4140
) {
@@ -62,7 +61,7 @@ public ApiResponse<List<BankResDTO.BankConnection>> getBanks( // TODO: List 객
6261
return ApiResponse.onSuccess(GeneralSuccessCode.OK, banks);
6362
}
6463

65-
@GetMapping("/banks/accounts")
64+
@GetMapping("/accounts")
6665
public ApiResponse<BankResDTO.BankAccountListDTO> getAllBankAccounts(
6766
// @CurrentMember Long memberId
6867
) {

src/main/java/org/umc/valuedi/domain/asset/controller/AssetControllerDocs.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public interface AssetControllerDocs {
8787
)
8888
)
8989
})
90-
ApiResponse<List<CardResDTO.CardConnection>> getCards();
90+
ApiResponse<CardResDTO.CardListDTO> getCards();
9191

9292
@Operation(summary = "연동된 카드사 목록 조회 API", description = "현재 사용자가 연동한 카드사 리스트를 조회합니다.")
9393
@ApiResponses({

src/main/java/org/umc/valuedi/domain/asset/converter/AssetConverter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.umc.valuedi.domain.asset.converter;
22

3+
import org.springframework.stereotype.Component;
34
import org.umc.valuedi.domain.asset.dto.res.AssetResDTO;
45
import org.umc.valuedi.domain.asset.dto.res.BankResDTO;
56
import org.umc.valuedi.domain.asset.dto.res.CardResDTO;
@@ -9,6 +10,7 @@
910
import java.util.List;
1011
import java.util.stream.Collectors;
1112

13+
@Component
1214
public class AssetConverter {
1315

1416
// 개별 BankAccount 엔티티 -> BankAccountInfo 변환

src/main/java/org/umc/valuedi/domain/asset/entity/BankTransaction.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,23 @@ public class BankTransaction extends BaseEntity {
4444
private Long afterBalance;
4545

4646
// 보낸분/받는분
47-
@Column(name = "desc1", length = 200)
47+
@Column(name = "desc1", length = 500)
4848
private String desc1;
4949

5050
// 거래구분/메모: 오픈뱅킹, 펌뱅킹, 체크우리, 모바일 등
51-
@Column(name = "desc2", length = 200)
51+
@Column(name = "desc2", length = 500)
5252
private String desc2;
5353

5454
// 적요(거래내용): 네이버페이 결제, 토스 OOO 등
55-
@Column(name = "desc3", length = 200)
55+
@Column(name = "desc3", length = 700)
5656
private String desc3;
5757

5858
// 거래점: 하나은행, OO지점 등
5959
@Column(name = "desc4", length = 200)
6060
private String desc4;
6161

6262
@Enumerated(EnumType.STRING)
63-
@Column(name = "direction", nullable = false, length = 10)
63+
@Column(name = "direction", nullable = false, length = 20)
6464
private TransactionDirection direction;
6565

6666
@Column(name = "order_seq")

src/main/java/org/umc/valuedi/domain/asset/entity/CardApproval.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import lombok.AccessLevel;
55
import lombok.AllArgsConstructor;
66
import lombok.Builder;
7+
import lombok.Getter;
78
import lombok.NoArgsConstructor;
89
import org.umc.valuedi.domain.asset.enums.CancelStatus;
910
import org.umc.valuedi.domain.asset.enums.HomeForeignType;
@@ -16,6 +17,7 @@
1617

1718
@Entity
1819
@Builder
20+
@Getter
1921
@NoArgsConstructor(access = AccessLevel.PROTECTED)
2022
@AllArgsConstructor(access = AccessLevel.PRIVATE)
2123
@Table(
@@ -99,4 +101,8 @@ public class CardApproval extends BaseEntity {
99101
@ManyToOne(fetch = FetchType.LAZY)
100102
@JoinColumn(name = "card_id", nullable = false)
101103
private Card card;
104+
105+
public void assignCard(Card card) {
106+
this.card = card;
107+
}
102108
}

src/main/java/org/umc/valuedi/domain/asset/repository/BankAccountRepository.java renamed to src/main/java/org/umc/valuedi/domain/asset/repository/bank/BankAccountRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.umc.valuedi.domain.asset.repository;
1+
package org.umc.valuedi.domain.asset.repository.bank;
22

33
import org.springframework.data.jpa.repository.JpaRepository;
44
import org.springframework.data.jpa.repository.Query;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.umc.valuedi.domain.asset.repository.bank;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.umc.valuedi.domain.asset.entity.BankTransaction;
5+
6+
public interface BankTransactionRepository extends JpaRepository<BankTransaction, Long>, BankTransactionRepositoryCustom {
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.umc.valuedi.domain.asset.repository.bank;
2+
3+
import org.umc.valuedi.domain.asset.entity.BankTransaction;
4+
import java.util.List;
5+
6+
public interface BankTransactionRepositoryCustom {
7+
void bulkInsert(List<BankTransaction> transactions);
8+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.umc.valuedi.domain.asset.repository.bank;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
5+
import org.springframework.jdbc.core.JdbcTemplate;
6+
import org.umc.valuedi.domain.asset.entity.BankTransaction;
7+
8+
import java.sql.PreparedStatement;
9+
import java.sql.SQLException;
10+
import java.sql.Timestamp;
11+
import java.util.List;
12+
13+
@RequiredArgsConstructor
14+
public class BankTransactionRepositoryImpl implements BankTransactionRepositoryCustom {
15+
16+
private final JdbcTemplate jdbcTemplate;
17+
18+
@Override
19+
public void bulkInsert(List<BankTransaction> transactions) {
20+
if (transactions.isEmpty()) {
21+
return;
22+
}
23+
24+
String sql = "INSERT IGNORE INTO bank_transaction " +
25+
"(bank_account_id, tr_date, tr_time, tr_datetime, out_amount, in_amount, after_balance, " +
26+
"desc1, desc2, desc3, desc4, direction, raw_json, synced_at, created_at, updated_at) " +
27+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
28+
29+
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
30+
@Override
31+
public void setValues(PreparedStatement ps, int i) throws SQLException {
32+
BankTransaction tx = transactions.get(i);
33+
ps.setLong(1, tx.getBankAccount().getId());
34+
ps.setObject(2, tx.getTrDate());
35+
ps.setObject(3, tx.getTrTime());
36+
37+
if (tx.getTrDatetime() != null) {
38+
ps.setTimestamp(4, Timestamp.valueOf(tx.getTrDatetime()));
39+
} else {
40+
ps.setNull(4, java.sql.Types.TIMESTAMP);
41+
}
42+
43+
ps.setLong(5, tx.getOutAmount());
44+
ps.setLong(6, tx.getInAmount());
45+
ps.setLong(7, tx.getAfterBalance());
46+
ps.setString(8, tx.getDesc1());
47+
ps.setString(9, tx.getDesc2());
48+
ps.setString(10, tx.getDesc3());
49+
ps.setString(11, tx.getDesc4());
50+
51+
String directionName = (tx.getDirection() != null) ? tx.getDirection().name() : null;
52+
ps.setString(12, directionName);
53+
54+
ps.setString(13, tx.getRawJson());
55+
56+
if (tx.getSyncedAt() != null) {
57+
ps.setTimestamp(14, Timestamp.valueOf(tx.getSyncedAt()));
58+
} else {
59+
ps.setNull(14, java.sql.Types.TIMESTAMP);
60+
}
61+
}
62+
63+
@Override
64+
public int getBatchSize() {
65+
return transactions.size();
66+
}
67+
});
68+
}
69+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.umc.valuedi.domain.asset.repository.card;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.umc.valuedi.domain.asset.entity.CardApproval;
5+
6+
public interface CardApprovalRepository extends JpaRepository<CardApproval, Long>, CardApprovalRepositoryCustom {
7+
}

0 commit comments

Comments
 (0)