-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/도메인 jpa 분리 #15
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
The head ref may contain hidden characters: "feat/\uB3C4\uBA54\uC778-JPA-\uBD84\uB9AC"
Feat/도메인 jpa 분리 #15
Changes from 16 commits
eddeea0
2ca92ec
fb3ac8c
1102c12
2c10dc9
a2e589a
970c785
5c9ce45
8dca41d
4a51797
bd45db8
1f071e2
eb8b0b0
45ad1f2
176d3fc
1c3291f
c811948
e59de82
7041d03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| PROFILES="${SPRING_PROFILES_ACTIVE:-backfill-public-id}" | ||
|
|
||
| ./gradlew bootRun --args="--spring.profiles.active=${PROFILES} --spring.main.web-application-type=none" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,13 @@ | ||
| package me.pinitnotification.domain.notification; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| public interface UpcomingScheduleNotificationRepository extends JpaRepository<UpcomingScheduleNotification, Long> { | ||
| public interface UpcomingScheduleNotificationRepository { | ||
| List<UpcomingScheduleNotification> findAll(); | ||
| Optional<UpcomingScheduleNotification> findByScheduleIdAndOwnerId(Long scheduleId, Long ownerId); | ||
| boolean existsByScheduleIdAndOwnerId(Long scheduleId, Long ownerId); | ||
| UpcomingScheduleNotification save(UpcomingScheduleNotification notification); | ||
| void deleteByScheduleIdAndOwnerId(Long scheduleId, Long ownerId); | ||
| void deleteAllInBatch(List<UpcomingScheduleNotification> notifications); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package me.pinitnotification.domain.shared; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| public interface IdGenerator { | ||
| UUID generate(); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||||||||
| package me.pinitnotification.infrastructure.batch; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import me.pinitnotification.domain.shared.IdGenerator; | ||||||||||||||||||||||||||||||||||||||||
| import org.slf4j.Logger; | ||||||||||||||||||||||||||||||||||||||||
| import org.slf4j.LoggerFactory; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.boot.CommandLineRunner; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.context.annotation.Profile; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.jdbc.core.JdbcTemplate; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Profile("backfill-public-id") | ||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||
| public class PublicIdBackfillRunner implements CommandLineRunner { | ||||||||||||||||||||||||||||||||||||||||
| private static final Logger log = LoggerFactory.getLogger(PublicIdBackfillRunner.class); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private final JdbcTemplate jdbcTemplate; | ||||||||||||||||||||||||||||||||||||||||
| private final IdGenerator idGenerator; | ||||||||||||||||||||||||||||||||||||||||
| private final int batchSize; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| public PublicIdBackfillRunner(JdbcTemplate jdbcTemplate, | ||||||||||||||||||||||||||||||||||||||||
| IdGenerator idGenerator, | ||||||||||||||||||||||||||||||||||||||||
| @Value("${backfill.public-id.batch-size:500}") int batchSize) { | ||||||||||||||||||||||||||||||||||||||||
| this.jdbcTemplate = jdbcTemplate; | ||||||||||||||||||||||||||||||||||||||||
| this.idGenerator = idGenerator; | ||||||||||||||||||||||||||||||||||||||||
| this.batchSize = batchSize; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||
| public void run(String... args) { | ||||||||||||||||||||||||||||||||||||||||
| backfillTable("upcoming_schedule_notification"); | ||||||||||||||||||||||||||||||||||||||||
| backfillTable("push_subscription"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private void backfillTable(String table) { | ||||||||||||||||||||||||||||||||||||||||
| int total = 0; | ||||||||||||||||||||||||||||||||||||||||
| while (true) { | ||||||||||||||||||||||||||||||||||||||||
| List<Long> ids = jdbcTemplate.query( | ||||||||||||||||||||||||||||||||||||||||
| "select id from " + table + " where public_id is null limit ?", | ||||||||||||||||||||||||||||||||||||||||
| ps -> ps.setInt(1, batchSize), | ||||||||||||||||||||||||||||||||||||||||
| (rs, rowNum) -> rs.getLong(1) | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (ids.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| for (Long id : ids) { | ||||||||||||||||||||||||||||||||||||||||
| int updated = jdbcTemplate.update( | ||||||||||||||||||||||||||||||||||||||||
| "update " + table + " set public_id = ? where id = ? and public_id is null", | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+51
to
+52
|
||||||||||||||||||||||||||||||||||||||||
| idGenerator.generate().toString(), | ||||||||||||||||||||||||||||||||||||||||
| id | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| total += updated; | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+56
|
||||||||||||||||||||||||||||||||||||||||
| for (Long id : ids) { | |
| int updated = jdbcTemplate.update( | |
| "update " + table + " set public_id = ? where id = ? and public_id is null", | |
| idGenerator.generate().toString(), | |
| id | |
| ); | |
| total += updated; | |
| int[] updatedCounts = jdbcTemplate.batchUpdate( | |
| "update " + table + " set public_id = ? where id = ? and public_id is null", | |
| ids, | |
| batchSize, | |
| (ps, id) -> { | |
| ps.setString(1, idGenerator.generate().toString()); | |
| ps.setLong(2, id); | |
| } | |
| ); | |
| for (int count : updatedCounts) { | |
| total += count; |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(1) 문제점: PublicIdBackfillRunner에 대한 테스트가 없습니다. 배치 작업의 정확성, 멱등성, 에러 처리 등을 검증하는 테스트가 필요합니다.
(2) 영향: 백필 로직에 버그가 있어도 프로덕션 환경에서만 발견될 수 있으며, 데이터 정합성 문제를 초래할 수 있습니다.
(3) 수정 제안: PublicIdBackfillRunnerTest를 추가하여 배치 처리 로직, 빈 테이블 처리, 부분 업데이트 등의 시나리오를 테스트해야 합니다.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package me.pinitnotification.infrastructure.persistence; | ||
|
|
||
| import me.pinitnotification.domain.shared.IdGenerator; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Component | ||
| public class IdV7GeneratorAdapter implements IdGenerator { | ||
| @Override | ||
| public UUID generate() { | ||
| return UuidV7Generator.generate(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package me.pinitnotification.infrastructure.persistence; | ||
|
|
||
| import java.util.UUID; | ||
| import java.util.concurrent.ThreadLocalRandom; | ||
|
|
||
| public final class UuidV7Generator { | ||
| private UuidV7Generator() { | ||
| } | ||
|
|
||
| public static UUID generate() { | ||
| byte[] bytes = new byte[16]; | ||
| long millis = System.currentTimeMillis(); | ||
|
|
||
| bytes[0] = (byte) (millis >>> 40); | ||
| bytes[1] = (byte) (millis >>> 32); | ||
| bytes[2] = (byte) (millis >>> 24); | ||
| bytes[3] = (byte) (millis >>> 16); | ||
| bytes[4] = (byte) (millis >>> 8); | ||
| bytes[5] = (byte) millis; | ||
|
|
||
| int randA = ThreadLocalRandom.current().nextInt(1 << 12); | ||
| bytes[6] = (byte) (0x70 | ((randA >>> 8) & 0x0F)); | ||
| bytes[7] = (byte) randA; | ||
|
|
||
| long randB = ThreadLocalRandom.current().nextLong(); | ||
| bytes[8] = (byte) (randB >>> 56); | ||
| bytes[9] = (byte) (randB >>> 48); | ||
| bytes[10] = (byte) (randB >>> 40); | ||
| bytes[11] = (byte) (randB >>> 32); | ||
| bytes[12] = (byte) (randB >>> 24); | ||
| bytes[13] = (byte) (randB >>> 16); | ||
| bytes[14] = (byte) (randB >>> 8); | ||
| bytes[15] = (byte) randB; | ||
|
|
||
| bytes[8] = (byte) ((bytes[8] & 0x3F) | 0x80); | ||
|
|
||
| long msb = 0; | ||
| long lsb = 0; | ||
| for (int i = 0; i < 8; i++) { | ||
| msb = (msb << 8) | (bytes[i] & 0xFF); | ||
| } | ||
| for (int i = 8; i < 16; i++) { | ||
| lsb = (lsb << 8) | (bytes[i] & 0xFF); | ||
| } | ||
| return new UUID(msb, lsb); | ||
| } | ||
| } | ||
|
Comment on lines
+10
to
+47
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(1) 문제점: SQL 인젝션 취약점이 있습니다. 테이블 이름이 직접 문자열 연결로 SQL 쿼리에 삽입되고 있습니다.
(2) 영향: 악의적인 테이블 이름이 전달될 경우 임의의 SQL이 실행될 수 있습니다.
(3) 수정 제안: 테이블 이름을 화이트리스트로 검증하거나, PreparedStatement의 테이블 이름 파라미터화를 사용할 수 없으므로 허용된 테이블 목록과 비교하여 검증해야 합니다.