Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<br>
<div align="center">
<img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/a57f82e1-43fa-44ea-b23e-bf39747a6cc5" width="193" height="193" />
<img src="https://github.com/Dev-Race/DevRace-backend/assets/56509933/e8dfa33b-8561-4181-9496-e3ec70f41aa8" width="193" height="193" />
<h3 align="center">Dev Race 🧑‍💻</h3>
<p align="center">
실시간 코딩 경쟁 및 채팅 서비스<br>
Expand All @@ -25,31 +25,32 @@


## 📄 Documents
- <strong>기간</strong>&nbsp;:&nbsp;&nbsp;2024.04.29 ~ 06.16

- #### [PM] 기획 명세서
- <details><summary>&nbsp;<a href="https://sahyunjin.notion.site/7384e0322e8d480c8f639ae1a84915fa?v=eda603b2a8844e6fa244492f502ba129&pvs=4">기능 상세 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/38c36139-1d4a-4075-bdf7-6ca33f222466" /></details>
- <details><summary>&nbsp;<a href="https://docs.google.com/spreadsheets/d/18bLgLlZGMPsulnnqDtMa3JtHX4kLwSqaXHfTSW5EAvw/edit?usp=sharing">WBS 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/b82c38a7-6e32-4038-b92f-7ded9b2ca113" /></details>
- <details><summary>&nbsp;<a href="https://sahyunjin.notion.site/7384e0322e8d480c8f639ae1a84915fa?v=eda603b2a8844e6fa244492f502ba129&pvs=4">기능 상세 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/38c36139-1d4a-4075-bdf7-6ca33f222466" /></details>
- <details><summary>&nbsp;<a href="https://docs.google.com/spreadsheets/d/18bLgLlZGMPsulnnqDtMa3JtHX4kLwSqaXHfTSW5EAvw/edit?usp=sharing">WBS 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/b82c38a7-6e32-4038-b92f-7ded9b2ca113" /></details>

- #### [BE] API 명세서
- <details><summary>&nbsp;<a href="https://sahyunjin.notion.site/2071d1695b254b78a1367ef555d6b820?v=336213c0a3b345f28fdb4ef181044d31&pvs=4">Rest API 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/863cd935-c292-4318-97e2-03b357528a27" /></details>
- <details><summary>&nbsp;<a href="https://sahyunjin.notion.site/2e577f50c85648cdaae86aeeae66be5a?v=a717219fcb494da39e765bea246b6cfd&pvs=4">WebSocket API 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/a5e52b5a-c3af-405c-89a7-43c24e5f1182" /></details>
- <details><summary>&nbsp;Swagger API 명세서</summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/222b7599-69ba-41aa-9ef5-613fd7837c54" /></details>
- <details><summary>&nbsp;<a href="https://sahyunjin.notion.site/2071d1695b254b78a1367ef555d6b820?v=336213c0a3b345f28fdb4ef181044d31&pvs=4">Rest API 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/863cd935-c292-4318-97e2-03b357528a27" /></details>
- <details><summary>&nbsp;<a href="https://sahyunjin.notion.site/2e577f50c85648cdaae86aeeae66be5a?v=a717219fcb494da39e765bea246b6cfd&pvs=4">WebSocket API 명세서</a></summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/a5e52b5a-c3af-405c-89a7-43c24e5f1182" /></details>
- <details><summary>&nbsp;Swagger API 명세서</summary><br><img src="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/222b7599-69ba-41aa-9ef5-613fd7837c54" /></details>
<br>



## 💻 Architecture

- ### System Architecture
![devrace_architecture drawio](https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/bafcab1e-77b4-4e9d-9db2-aac15c130eed)
![devrace_architecture drawio](https://github.com/Dev-Race/DevRace-backend/assets/56509933/103fd5a2-1ba8-4365-81ea-68b11b8218d8)

- ### Network Architecture
![devrace_network_architecture drawio](https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/9918f3c3-f0b5-4eb2-a712-19c665737576)
![devrace_network_architecture drawio](https://github.com/Dev-Race/DevRace-backend/assets/56509933/a43dae33-515c-4cd0-b8fe-39bfe56d7ab7)

- ### Notification in Architecture
![slack collaboration_alarm](https://github.com/Dev-Race/DevRace-backend/assets/56509933/888fd684-76bf-4c77-a25b-d4a2ca3daf16)

&#8594;&nbsp;&nbsp;<strong>Slack Notifications</strong> sent by the <strong>GitHub Actions</strong> :<br>
&#8594;&nbsp;&nbsp;<strong>Slack Notifications</strong> sent by the <strong>GitHub Actions</strong>&nbsp;:<br>
`💡 PR Open` `💬 PR Review` `❌ CI/CD Fail` `✅ CI/CD Success`
<br><br>

Expand All @@ -67,13 +68,13 @@ Backend|Security|Database|Deployment|Other|
- Documentation : Notion, Swagger
- Notification : Slack
```
&#8594;&nbsp;***Version***&nbsp;:&nbsp;&nbsp;Java 17 · Spring Boot 3.1.11
&#8594;&nbsp;&nbsp;***BE Version***&nbsp;:&nbsp;&nbsp;Java 17 · Spring Boot 3.1.11
<br><br>



## 🗂️ Database
![devrace DB ERD](https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/4b0db7b7-d530-4acf-9361-c1689d48f97a)
![devrace DB ERD](https://github.com/Dev-Race/DevRace-backend/assets/56509933/cc34957c-5429-4fd0-b95d-8c9d9024f77e)
<br><br>


Expand Down Expand Up @@ -177,7 +178,7 @@ ex) [#32] Feat: 소셜 로그인 및 회원가입 기능
<br>

```
├── .ebextensions-dev
├── .ebextensions
│ ├── 00-set-timezone.config
│ ├── 01-set-swapmemory.config
│ └── 02-rabbitmq.config
Expand Down Expand Up @@ -314,5 +315,5 @@ ex) [#32] Feat: 소셜 로그인 및 회원가입 기능
## 👨‍👩‍👧‍👧 Team
| [사현진](https://github.com/tkguswls1106) | [장준상](https://github.com/JunSang1121) | [정연재](https://github.com/zzangjyj0818) | [권인우](https://github.com/inwoo0206) | [이정향](https://github.com/Hyanggggg) |
| :----------------------------------------: | :----------------------------------------: | :----------------------------------------: | :----------------------------------------: | :----------------------------------------: |
| <img width = "300" src ="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/6f1a9df8-8511-466c-b22e-93c55f9919ac"><br>- Team Leader - | <img width = "300" src ="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/f872cd42-cfa2-4818-a717-75451450382e"> | <img width = "300" src ="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/3794fbbb-20d5-4752-956e-4560bd15e1f0"> | <img width = "300" src ="https://github.com/tkguswls1106/DevRace-Readme/assets/56509933/866c8eac-3b18-453d-8e33-e5fa06817a89"> | <img width = "300" src ="https://github.com/2023-Hackathon-TeamSMUD/.github/assets/56509933/a563b39b-6d80-40b1-930f-311455b21e82"> |
| <img width = "300" src ="https://github.com/Dev-Race/DevRace-backend/assets/56509933/20a67908-ca87-49ea-aafc-60f306302429"><br>- Team Leader - | <img width = "300" src ="https://github.com/Dev-Race/DevRace-backend/assets/56509933/2223bac3-48ed-4a98-9f0f-6bd860a080ac"> | <img width = "300" src ="https://github.com/Dev-Race/DevRace-backend/assets/56509933/798cc442-77f0-4b81-a61e-56aaedf784d5"> | <img width = "300" src ="https://github.com/Dev-Race/DevRace-backend/assets/56509933/79fe6476-58ae-42ce-83ca-2dd6a59c4585"> | <img width = "300" src ="https://github.com/Dev-Race/DevRace-backend/assets/56509933/e9ef0c57-73cb-4b2c-9b61-f9773fb02f5b"> |
| Project Manager,<br>DevOps Engineer,<br>BE Developer | Backend Developer | Frontend Developer | Frontend Developer | Designer |
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,22 @@ public int getBatchSize() {
}
});
}

public void batchDelete(List<UserRoom> userRoomList) {
String sql = "DELETE FROM user_room WHERE user_room_id = ?";

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
UserRoom userRoom = userRoomList.get(i);
ps.setLong(1, userRoom.getId());
}

@Override
public int getBatchSize() {
return userRoomList.size();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.sajang.devracebackend.dto.auth.TokenDto;
import com.sajang.devracebackend.dto.user.UserResponseDto;
import com.sajang.devracebackend.repository.UserRepository;
import com.sajang.devracebackend.repository.UserRoomBatchRepository;
import com.sajang.devracebackend.repository.UserRoomRepository;
import com.sajang.devracebackend.response.exception.Exception400;
import com.sajang.devracebackend.security.jwt.TokenProvider;
Expand All @@ -33,6 +34,7 @@ public class AuthServiceImpl implements AuthService {
private final UserService userService;
private final UserRepository userRepository;
private final UserRoomRepository userRoomRepository;
private final UserRoomBatchRepository userRoomBatchRepository;
private final TokenProvider tokenProvider;


Expand Down Expand Up @@ -121,10 +123,12 @@ public TokenDto reissue(ReissueRequestDto reissueRequestDto) { // Refresh Token
@Override
public void withdrawal() {
User user = userService.findLoginUser();
List<UserRoom> userRoomList = user.getUserRoomList();

// 자식 UserRoom 삭제 - hard delete
List<UserRoom> userRoomList = user.getUserRoomList();
userRoomRepository.deleteAll(userRoomList);
// userRoomRepository.deleteAll(userRoomList); // JPA의 deleteAll()은 SQL 쿼리가 UserRoom 개수만큼 날아가는 문제가 있음.
// userRoomRepository.deleteAllInBatch(userRoomList); // JPA의 deleteAllInBatch()은 10000개 이상의 데이터 처리시 stackoverflow 에러가 발생함.
userRoomBatchRepository.batchDelete(userRoomList); // JDBC의 batch delete를 활용하여, 대용량 Batch 삭제 처리가 가능함. -> DB 여러번 접근 방지 & 성능 향상

// 부모 User 삭제 - soft delete
user.deleteAccount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ public void usersEnterRoom(Long roomId) {
.build())
.collect(Collectors.toList());

// JDBC batch insert로 UserRoom 한번에 저장 (여러번 DB에 접근하는것을 방지.)
// userRoomRepository.saveAll(userRoomList);
userRoomBatchRepository.batchInsert(userRoomList); // 위 JPA의 saveAll()은 기본키가 IDENTITY 전략인 테이블에는 batch 처리가 적용되지않음.
// userRoomRepository.saveAll(userRoomList); // JPA의 saveAll()은 기본키가 IDENTITY 전략인 테이블에는 batch 처리가 적용되지않는 문제가 있음.
userRoomBatchRepository.batchInsert(userRoomList); // JDBC의 batch insert를 활용하여, 대용량 Batch 저장 처리가 가능함. -> DB 여러번 접근 방지 & 성능 향상

// Room RoomState=START로 수정
room.updateRoomState(RoomState.START);
Expand Down
107 changes: 107 additions & 0 deletions src/test/java/com/sajang/devracebackend/service/AuthServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.sajang.devracebackend.service;

import com.sajang.devracebackend.domain.Room;
import com.sajang.devracebackend.domain.User;
import com.sajang.devracebackend.domain.enums.Role;
import com.sajang.devracebackend.domain.mapping.UserRoom;
import com.sajang.devracebackend.repository.RoomRepository;
import com.sajang.devracebackend.repository.UserRepository;
import com.sajang.devracebackend.repository.UserRoomBatchRepository;
import com.sajang.devracebackend.repository.UserRoomRepository;
import com.sajang.devracebackend.security.jwt.TokenProvider;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

// @SpringBootTest
public class AuthServiceTest {

@Autowired
private AuthService authService;
@Autowired
private UserRepository userRepository;
@Autowired
private RoomRepository roomRepository;
@Autowired
private UserRoomRepository userRoomRepository;
@Autowired
private UserRoomBatchRepository userRoomBatchRepository;
@Autowired
private TokenProvider tokenProvider;
@Autowired
private JdbcTemplate jdbcTemplate;


// @Test
@Transactional
@DisplayName("회원탈퇴 - JDBC Batch Delete Test") // UserRooms Batch Delete 시간 측정
void withdrawal_Test() {

// 기초 데이터 관리
Integer inputUserRoomsCount = 10000; // 10000개의 UserRooms 더미데이터 생성
Long userId = 1L; // user_id=1인 User 엔티티 활용 예정
Long roomId = 1L; // room_id=1인 Room 엔티티 활용 예정

// UserRoom 더미데이터 생성
Integer startUserRoomsCount = userRoomRepository.findAll().size(); // 초기 UserRooms 데이터 개수 측정
List<UserRoom> userRoomList = makeFakeUserRooms(userId, roomId, inputUserRoomsCount);
userRoomBatchRepository.batchInsert(userRoomList);

// 로그인
String jwt = tokenProvider.generateAccessToken(userId, Role.ROLE_USER);
Authentication authentication = tokenProvider.getAuthentication(jwt); // 사용자를 인증
SecurityContextHolder.getContext().setAuthentication(authentication); // SecurityContextHolder에 인증 정보를 설정

// '회원탈퇴' -> UserRooms 더미데이터 삭제
LocalDateTime startTime = LocalDateTime.now(); // 시작시각 기록
authService.withdrawal();
LocalDateTime endTime = LocalDateTime.now(); // 종료시각 기록
Integer endUserRoomsCount = userRoomRepository.findAll().size(); // 삭제후 UserRooms 데이터 개수 측정

// 실행시간 출력
String printTime = getPrintTime(startTime, endTime);
System.out.println("\n< JDBC Batch Delete 사용 (JPA deleteAll X, JPA deleteAllInBatch X) >");
System.out.println(String.format("- %d개 방의 회원탈퇴 실행시간:", inputUserRoomsCount) + printTime); // 출력

// DB 롤백 검증
assertThat(startUserRoomsCount).isEqualTo(endUserRoomsCount); // UserRooms 더미데이터 삭제 검증
}


// ========== 유틸성 메소드 ========== //

public List<UserRoom> makeFakeUserRooms(Long userId, Long roomId, int userRoomsCount) { // UserRooms 더미데이터 생성
User user = userRepository.findById(userId).orElseThrow();
Room room = roomRepository.findById(roomId).orElseThrow();

List<UserRoom> userRoomList = new ArrayList<>();
for(int i=0; i<userRoomsCount; i++) {
UserRoom userRoom = UserRoom.UserRoomSaveBuilder()
.user(user)
.room(room)
.build();
userRoomList.add(userRoom);
}
return userRoomList;
}

public String getPrintTime(LocalDateTime startTime, LocalDateTime endTime) { // 실행시간 출력 메세지 반환
Duration duration = Duration.between(startTime, endTime); // 메소드 실행시간 측정
long milliseconds = duration.toMillis(); // 밀리초(ms)
double seconds = milliseconds / 1000.0; // 밀리초를 초로 변환(s)
String printTime = String.format(" %dms (%.2fs)\n", milliseconds, seconds);
return printTime;
}
}
Loading