-
Notifications
You must be signed in to change notification settings - Fork 6
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
[BE] Refactor/#430 핀 복사 시 JdbcTemplate을 이용하여 bulk insert하도록 변경 #617
Changes from 17 commits
ca47a30
11f64ff
331db76
2486358
79dcbce
12ab047
974de2b
5186cc8
e6a30ba
60cab90
dc9a5b7
f907795
26db14d
585e135
7512b63
0073ed2
a57a16c
de403b5
58fc4ea
ab519fb
eabc420
bd7f38c
6976452
bda1f24
986ffb1
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,11 @@ | ||
package com.mapbefine.mapbefine.common.repository; | ||
|
||
import com.mapbefine.mapbefine.pin.domain.Pin; | ||
import com.mapbefine.mapbefine.topic.domain.Topic; | ||
import java.util.List; | ||
|
||
public interface PinBatchRepositoryCustom { | ||
|
||
int[] saveAllToTopic(Topic topicForCopy, List<Pin> originalPins); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package com.mapbefine.mapbefine.common.repository; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 전 pin 패키지 내에 infrastructure 패키지 만들어 준 후에 거기서 사용하는 걸로 생각했었어요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 아직 Draft 상태인 PR이라.. 본문 작성 및 보완하고 나면 Ready for Review로 전환 후 다시 말씀드리겠습니다 ㅠ |
||
|
||
import com.mapbefine.mapbefine.pin.domain.Pin; | ||
import com.mapbefine.mapbefine.pin.domain.PinImage; | ||
import com.mapbefine.mapbefine.topic.domain.Topic; | ||
import java.sql.PreparedStatement; | ||
import java.sql.SQLException; | ||
import java.sql.Timestamp; | ||
import java.time.LocalDateTime; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.IntStream; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.jdbc.core.BatchPreparedStatementSetter; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Slf4j | ||
@Repository | ||
public class PinBatchRepositoryCustomImpl implements PinBatchRepositoryCustom { | ||
|
||
private final JdbcTemplate jdbcTemplate; | ||
|
||
public PinBatchRepositoryCustomImpl(JdbcTemplate jdbcTemplate) { | ||
this.jdbcTemplate = jdbcTemplate; | ||
} | ||
|
||
public int[] saveAllToTopic(Topic topicForCopy, List<Pin> originalPins) { | ||
int[] rowCount = batchUpdatePins(topicForCopy, originalPins); | ||
|
||
Long firstIdFromBatch = jdbcTemplate.queryForObject("SELECT last_insert_id()", Long.class); | ||
validateId(firstIdFromBatch); | ||
List<PinImageInsertDto> pinImageInsertDtos = createPinImageDTOsToBatch(originalPins, rowCount, | ||
firstIdFromBatch); | ||
|
||
if (pinImageInsertDtos.isEmpty()) { | ||
return rowCount; | ||
} | ||
return batchUpdatePinImages(pinImageInsertDtos); | ||
} | ||
|
||
private int[] batchUpdatePins( | ||
Topic topicForCopy, | ||
List<Pin> originalPins | ||
) { | ||
LocalDateTime createdAt = topicForCopy.getLastPinUpdatedAt(); | ||
|
||
String bulkInsertSql = "INSERT INTO pin (" | ||
+ "name, description, member_id, topic_id, location_id," | ||
+ " created_at, updated_at)" | ||
+ " VALUES (" | ||
+ " ?, ?, ?, ?, ?," | ||
+ " ?, ?)"; | ||
log.debug("[Query] bulk insert size {} : {}", originalPins.size(), bulkInsertSql); | ||
|
||
Long topicId = topicForCopy.getId(); | ||
Long creatorId = topicForCopy.getCreator().getId(); | ||
|
||
return jdbcTemplate.batchUpdate(bulkInsertSql, new BatchPreparedStatementSetter() { | ||
@Override | ||
public void setValues(PreparedStatement ps, int i) throws SQLException { | ||
Pin pin = originalPins.get(i); | ||
ps.setString(1, pin.getName()); | ||
ps.setString(2, pin.getDescription()); | ||
ps.setLong(3, creatorId); | ||
ps.setLong(4, topicId); | ||
ps.setLong(5, pin.getLocation().getId()); | ||
ps.setTimestamp(6, Timestamp.valueOf(createdAt)); | ||
ps.setTimestamp(7, Timestamp.valueOf(createdAt)); | ||
log.trace("[Parameter Binding] {} : " | ||
+ "name={}, description={}, member_id={}, topic_id={}, location_id={}, " | ||
+ "created_at={}, updated_at={}", | ||
i, pin.getName(), pin.getDescription(), creatorId, topicId, pin.getLocation().getId(), | ||
createdAt, createdAt); | ||
} | ||
|
||
@Override | ||
public int getBatchSize() { | ||
return originalPins.size(); | ||
} | ||
}); | ||
} | ||
|
||
private void validateId(Long firstIdFromBatch) { | ||
if (Objects.isNull(firstIdFromBatch)) { | ||
throw new IllegalStateException("fail to batch update pins"); | ||
} | ||
} | ||
|
||
private List<PinImageInsertDto> createPinImageDTOsToBatch( | ||
List<Pin> originalPins, | ||
int[] rowCount, | ||
Long firstIdFromBatch | ||
) { | ||
return IntStream.range(0, originalPins.size()) | ||
.filter(index -> rowCount[index] != -3) | ||
.mapToObj(index -> { | ||
Pin pin = originalPins.get(index); | ||
return PinImageInsertDto.of(pin.getPinImages(), firstIdFromBatch + index); | ||
}).flatMap(Collection::stream) | ||
.toList(); | ||
} | ||
|
||
private int[] batchUpdatePinImages(List<PinImageInsertDto> pinImages) { | ||
String bulkInsertSql = "INSERT INTO pin_image " | ||
+ "(image_url, pin_id) " | ||
+ "VALUES " | ||
+ "(?, ?)"; | ||
log.debug("[Query] bulk insert size {} : {}", pinImages.size(), bulkInsertSql); | ||
|
||
return jdbcTemplate.batchUpdate(bulkInsertSql, new BatchPreparedStatementSetter() { | ||
@Override | ||
public void setValues(PreparedStatement ps, int i) throws SQLException { | ||
PinImageInsertDto pinImage = pinImages.get(i); | ||
ps.setString(1, pinImage.getImageUrl()); | ||
ps.setLong(2, pinImage.getPinId()); | ||
log.trace("[Parameter Binding] {} : imageUrl={}, pinImage={} ", | ||
i, pinImage.getImageUrl(), pinImage.getPinId()); | ||
} | ||
|
||
@Override | ||
public int getBatchSize() { | ||
return pinImages.size(); | ||
} | ||
}); | ||
} | ||
|
||
private static class PinImageInsertDto { | ||
|
||
private final String imageUrl; | ||
private final Long pinId; | ||
private final boolean isDeleted; | ||
|
||
private PinImageInsertDto(String imageUrl, Long pinId, boolean isDeleted) { | ||
this.imageUrl = imageUrl; | ||
this.pinId = pinId; | ||
this.isDeleted = isDeleted; | ||
} | ||
|
||
public static PinImageInsertDto of(PinImage pinImage, Long pinId) { | ||
return new PinImageInsertDto( | ||
pinImage.getImageUrl(), | ||
pinId, | ||
pinImage.isDeleted() | ||
); | ||
} | ||
|
||
private static List<PinImageInsertDto> of(List<PinImage> pinImages, Long pinId) { | ||
return pinImages.stream() | ||
.map(pinImage -> PinImageInsertDto.of(pinImage, pinId)) | ||
.toList(); | ||
} | ||
|
||
public String getImageUrl() { | ||
return imageUrl; | ||
} | ||
|
||
public Long getPinId() { | ||
return pinId; | ||
} | ||
|
||
public boolean isDeleted() { | ||
return isDeleted; | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package com.mapbefine.mapbefine.pin.domain; | ||
|
||
import com.mapbefine.mapbefine.common.repository.PinBatchRepositoryCustom; | ||
import java.util.List; | ||
import org.springframework.data.jpa.repository.EntityGraph; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
@@ -9,11 +10,14 @@ | |
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface PinRepository extends JpaRepository<Pin, Long> { | ||
public interface PinRepository extends JpaRepository<Pin, Long>, PinBatchRepositoryCustom { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
@EntityGraph(attributePaths = {"location", "topic", "creator", "pinImages"}) | ||
List<Pin> findAll(); | ||
|
||
@EntityGraph(attributePaths = {"location", "topic", "creator", "pinImages"}) | ||
List<Pin> findAllByIdIn(List<Long> pinIds); | ||
|
||
@EntityGraph(attributePaths = {"location", "topic", "creator", "pinImages"}) | ||
List<Pin> findAllByTopicId(Long topicId); | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -22,6 +22,7 @@ | |||||||
import com.mapbefine.mapbefine.topic.dto.request.TopicUpdateRequest; | ||||||||
import com.mapbefine.mapbefine.topic.exception.TopicException.TopicBadRequestException; | ||||||||
import com.mapbefine.mapbefine.topic.exception.TopicException.TopicForbiddenException; | ||||||||
import java.time.LocalDateTime; | ||||||||
import java.util.Collection; | ||||||||
import java.util.List; | ||||||||
import java.util.NoSuchElementException; | ||||||||
|
@@ -55,12 +56,12 @@ public Long saveTopic(AuthMember member, TopicCreateRequest request) { | |||||||
Topic topic = convertToTopic(member, request); | ||||||||
List<Long> pinIds = request.pins(); | ||||||||
|
||||||||
topicRepository.save(topic); | ||||||||
topicRepository.flush(); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JdbcTemplate은 바로 쿼리문을 쏘니까 필요한 듯요.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다들 꼼꼼히 봐주셔서 감사합니다!!
맞아요 저도 그런 이유로 flush를 넣었었는데요.. (JdbcTemplate의 쿼리 이전에 topic을 먼저 save하려고) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
은 어떠신가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헐 댑악 ~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 근데 flush 필요 없는 것 맞았습니다 ^.^ ㅜ 지울게요! 좋은거 알려주셔서 감사해여 |
||||||||
if (!pinIds.isEmpty()) { | ||||||||
kpeel5839 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
copyPinsToTopic(member, topic, pinIds); | ||||||||
} | ||||||||
|
||||||||
topicRepository.save(topic); | ||||||||
|
||||||||
return topic.getId(); | ||||||||
} | ||||||||
|
||||||||
|
@@ -105,14 +106,15 @@ private void copyPinsToTopic( | |||||||
) { | ||||||||
List<Pin> originalPins = findAllPins(pinIds); | ||||||||
validateCopyablePins(member, originalPins); | ||||||||
topic.increasePinCount(pinIds.size()); | ||||||||
topic.updateLastPinUpdatedAt(LocalDateTime.now()); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @PrePersist
protected void prePersist() {
lastPinUpdatedAt = LocalDateTime.now();
} 현재 그렇기 때문에, 아니라면 따끔하게 말씀해주십숑 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉 맞아요!! id 채번하는 데에만 정신이 팔려서 꼼꼼하지 못했던 부분이 많네요 ㅠㅠ 감사합니당 |
||||||||
pinRepository.flush(); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기도 flush 가 있는 것을 보니 batch update 를 진행하기 위해서는 명시적인 flush 호출이 필요한가보군요 흠흠 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. update할때는 flush를 해주어야 하는 게 맞아요! |
||||||||
|
||||||||
Member creator = findCreatorByAuthMember(member); | ||||||||
|
||||||||
originalPins.forEach(pin -> pin.copyToTopic(creator, topic)); | ||||||||
pinRepository.saveAllToTopic(topic, originalPins); | ||||||||
} | ||||||||
|
||||||||
private List<Pin> findAllPins(List<Long> pinIds) { | ||||||||
List<Pin> findPins = pinRepository.findAllById(pinIds); | ||||||||
List<Pin> findPins = pinRepository.findAllByIdIn(pinIds); | ||||||||
|
||||||||
if (pinIds.size() != findPins.size()) { | ||||||||
throw new PinBadRequestException(ILLEGAL_PIN_ID); | ||||||||
|
@@ -134,15 +136,14 @@ private void validateCopyablePins(AuthMember member, List<Pin> originalPins) { | |||||||
public Long merge(AuthMember member, TopicMergeRequest request) { | ||||||||
Topic topic = convertToTopic(member, request); | ||||||||
List<Topic> originalTopics = findAllTopics(request.topics()); | ||||||||
|
||||||||
validateCopyableTopics(member, originalTopics); | ||||||||
|
||||||||
Member creator = findCreatorByAuthMember(member); | ||||||||
List<Pin> originalPins = getAllPinsFromTopics(originalTopics); | ||||||||
originalPins.forEach(pin -> pin.copyToTopic(creator, topic)); | ||||||||
|
||||||||
topicRepository.save(topic); | ||||||||
topic.increasePinCount(originalPins.size()); | ||||||||
topic.updateLastPinUpdatedAt(LocalDateTime.now()); | ||||||||
|
||||||||
topicRepository.save(topic); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어라! 여기는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 엥 그러네 ... |
||||||||
pinRepository.saveAllToTopic(topic, originalPins); | ||||||||
return topic.getId(); | ||||||||
} | ||||||||
|
||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,6 +152,15 @@ public void removeImage() { | |
this.topicInfo = topicInfo.removeImage(); | ||
} | ||
|
||
public void increasePinCount() { | ||
pinCount++; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 사용되고 있지 않는 메서드로 보입니다! |
||
|
||
public void increasePinCount(int count) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기 개행 ^.^ |
||
pinCount += count; | ||
} | ||
|
||
public void decreasePinCount() { | ||
pinCount--; | ||
} | ||
|
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.
Collection으로 반환은 안되는건가용?
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.
Draft 상태지만 미리 남겨주셔서 말씀드리자면, 기존 batchUpdate의 시그니처를 그대로 가져가고자 했습니다!
batchUpdate에서 변경된 행 수를 int[] 로 주는데, 굳이 Collection으로 바꿔줄 이유를 모르겠어서용