Skip to content

Commit

Permalink
feat: 저장된 채팅 1분마다 DB에 저장
Browse files Browse the repository at this point in the history
- scheduler를 사용하여 1분마다 DB에 채팅 저장
- Batch insert를 사용하여 한 번에 저장
  • Loading branch information
Leehunil committed Dec 4, 2024
1 parent cc93a9e commit 64214e3
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
59 changes: 59 additions & 0 deletions src/main/java/com/palettee/chat/repository/ChatJdbcRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.palettee.chat.repository;

import com.palettee.chat.domain.Chat;
import com.palettee.chat.domain.ChatImage;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class ChatJdbcRepository {

private final JdbcTemplate jdbcTemplate;

public void batchInsertChats(List<Chat> chats, List<ChatImage> chatImages) {
String sql = "INSERT INTO chat"
+ "(chat_id, user_id, chat_room_id, content, send_at) VALUE(?, ?, ?, ?, ?)";

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Chat chat = chats.get(i);
ps.setString(1, chat.getId());
ps.setLong(2, chat.getUser().getId());
ps.setLong(3, chat.getChatRoom().getId());
ps.setString(4, chat.getContent());
ps.setTimestamp(5, Timestamp.valueOf(chat.getSendAt()));
}

@Override
public int getBatchSize() {
return chats.size();
}
});

String chatImageSql = "INSERT INTO chat_image"
+ "(chat_id, image_url) VALUE(?, ?)";
jdbcTemplate.batchUpdate(chatImageSql, new BatchPreparedStatementSetter() {

@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ChatImage chatImage = chatImages.get(i);
ps.setString(1, chatImage.getChat().getId());
ps.setString(2, chatImage.getImageUrl());
}

@Override
public int getBatchSize() {
return chatImages.size();
}
});
}
}
59 changes: 59 additions & 0 deletions src/main/java/com/palettee/chat/service/ChatRedisService.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,66 @@ public ChatResponse addChat(String email, Long chatRoomId, ChatRequest chatReque
return chatResponse;
}

public ChatCustomResponse getChats(Long chatRoomId, int size, LocalDateTime lastSendAt) {
String chatRoomIdStr = TypeConverter.LongToString(chatRoomId);

long offset = 0;
LocalDateTime findSendAt = lastSendAt;

if (lastSendAt == null) {
log.info("Local = {}",LocalDateTime.now());
findSendAt = LocalDateTime.now();
} else {
offset = 1;
}

Double cursor = TypeConverter.LocalDateTimeToDouble(findSendAt);
log.info("cursor = {}", cursor);
Set<ChatResponse> objects
= zSetOperations.reverseRangeByScore(chatRoomIdStr, Double.NEGATIVE_INFINITY, cursor, offset, size+1);
List<ChatResponse> results = objects.stream().collect(Collectors.toList());

log.info("results size = {}", results.size());

if(results.size() <= size) {
ChatCustomResponse chatDataInDB = findOtherChatDataInDB(results, lastSendAt, chatRoomId, size - results.size());

if(!results.isEmpty()) {
for(ChatResponse chatResponse : chatDataInDB.getChats()) {
results.add(chatResponse);
}
return ChatCustomResponse.toResponseFromDto(results, chatDataInDB.isHasNext(), chatDataInDB.getLastSendAt());
}

return chatDataInDB;
}

LocalDateTime nextSendAt = TypeConverter.StringToLocalDateTime(results.get(size - 1).getSendAt());
List<ChatResponse> chats = results.subList(0, size);
return ChatCustomResponse.toResponseFromDto(chats, true, nextSendAt);
}

public ChatCustomResponse findOtherChatDataInDB(List<ChatResponse> results, LocalDateTime lastSendAt,
Long chatRoomId, int size) {
if(!results.isEmpty()) {
lastSendAt = TypeConverter.StringToLocalDateTime(results.get(results.size() - 1).getSendAt());
}
ChatCustomResponse chatNoOffset = chatCustomRepository.findChatNoOffset(chatRoomId, size, lastSendAt);

if (!chatNoOffset.getChats().isEmpty()) {
cachingDBDataToRedis(chatNoOffset.getChats());
}
return chatNoOffset;
}

public void cachingDBDataToRedis(List<ChatResponse> chatsInDB) {
for(ChatResponse chatResponse : chatsInDB) {
LocalDateTime sendAt = TypeConverter.StringToLocalDateTime(chatResponse.getSendAt());
redisTemplate
.opsForZSet()
.add(TypeConverter.LongToString(chatResponse.getChatRoomId()), chatResponse, TypeConverter.LocalDateTimeToDouble(sendAt));
}
}

private User getUser(String email) {
return userRepository
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.palettee.chat.service;

import com.palettee.chat.controller.dto.response.ChatResponse;
import com.palettee.chat.domain.Chat;
import com.palettee.chat.domain.ChatImage;
import com.palettee.chat.repository.ChatJdbcRepository;
import com.palettee.chat_room.domain.ChatRoom;
import com.palettee.chat_room.service.ChatRoomService;
import com.palettee.user.domain.User;
import com.palettee.user.exception.UserNotFoundException;
import com.palettee.user.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
@Slf4j
public class ChatWriteBackScheduling {
private final RedisTemplate<String, ChatResponse> redisTemplate;
private final UserRepository userRepository;
private final ChatRoomService chatRoomService;
private final ChatJdbcRepository chatJdbcRepository;

public ChatWriteBackScheduling(@Qualifier("chatRedisTemplate") RedisTemplate<String, ChatResponse> redisTemplate,
UserRepository userRepository,
ChatRoomService chatRoomService,
ChatJdbcRepository chatJdbcRepository) {
this.redisTemplate = redisTemplate;
this.userRepository = userRepository;
this.chatRoomService = chatRoomService;
this.chatJdbcRepository = chatJdbcRepository;
}

// Todo user와 chatRoom 조회는 성능이 떨어진다. 그니깐 중간에 DTO를 하나 두어서 필요한 정보만 넣고 batchInsert를 하자.
// Todo 하지만 연관관계 매핑에서 문제가 발생함 이 부분 질문 사항
@Scheduled(cron = "0 0/1 * * * *")
public void writeBack() {
BoundZSetOperations<String, ChatResponse> setOperation = redisTemplate.boundZSetOps("NEW_CHAT");

ScanOptions scanOptions = ScanOptions.scanOptions().build();

List<Chat> chatList = new ArrayList<>();
List<ChatImage> chatImageList = new ArrayList<>();

Cursor<ZSetOperations.TypedTuple<ChatResponse>> cursor = setOperation.scan(scanOptions);
while (cursor.hasNext()) {
ZSetOperations.TypedTuple<ChatResponse> tupleChatResponse = cursor.next();
ChatResponse chatResponse = tupleChatResponse.getValue();

User user = getUser(chatResponse.getEmail());
ChatRoom chatRoom = chatRoomService.getChatRoom(chatResponse.getChatRoomId());

Chat chatEntity = chatResponse.toChatEntity(user, chatRoom);
chatList.add(chatEntity);

if(chatResponse.getImgUrls() != null || !chatResponse.getImgUrls().isEmpty()) {
for(ChatImage chatImage : chatResponse.toChatImageEntities(chatEntity)) {
chatImageList.add(chatImage);
}
}
}

if(!chatList.isEmpty() || !chatImageList.isEmpty()) {
chatJdbcRepository.batchInsertChats(chatList, chatImageList);
redisTemplate.delete("NEW_CHAT");
}
}

private User getUser(String email) {
return userRepository
.findByEmail(email)
.orElseThrow(() -> UserNotFoundException.EXCEPTION);
}
}

0 comments on commit 64214e3

Please sign in to comment.