diff --git a/README.md b/README.md index 92d4cd6..2a427a1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- +

Dev Race ๐Ÿง‘โ€๐Ÿ’ป

์‹ค์‹œ๊ฐ„ ์ฝ”๋”ฉ ๊ฒฝ์Ÿ ๋ฐ ์ฑ„ํŒ… ์„œ๋น„์Šค
@@ -25,15 +25,16 @@ ## ๐Ÿ“„ Documents +- ๊ธฐ๊ฐ„ :  2024.04.29 ~ 06.16 - #### [PM] ๊ธฐํš ๋ช…์„ธ์„œ - -

 ๊ธฐ๋Šฅ ์ƒ์„ธ ๋ช…์„ธ์„œ
- -
 WBS ๋ช…์„ธ์„œ
+ -
 ๊ธฐ๋Šฅ ์ƒ์„ธ ๋ช…์„ธ์„œ
+ -
 WBS ๋ช…์„ธ์„œ
- #### [BE] API ๋ช…์„ธ์„œ - -
 Rest API ๋ช…์„ธ์„œ
- -
 WebSocket API ๋ช…์„ธ์„œ
- -
 Swagger API ๋ช…์„ธ์„œ
+ -
 Rest API ๋ช…์„ธ์„œ
+ -
 WebSocket API ๋ช…์„ธ์„œ
+ -
 Swagger API ๋ช…์„ธ์„œ

@@ -41,15 +42,15 @@ ## ๐Ÿ’ป 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) -→  Slack Notifications sent by the GitHub Actions :
+→  Slack Notifications sent by the GitHub Actions :
`๐Ÿ’ก PR Open` `๐Ÿ’ฌ PR Review` `โŒ CI/CD Fail` `โœ… CI/CD Success`

@@ -67,13 +68,13 @@ Backend|Security|Database|Deployment|Other| - Documentation : Notion, Swagger - Notification : Slack ``` -→ ***Version*** :  Java 17 ยท Spring Boot 3.1.11 +→  ***BE Version*** :  Java 17 ยท Spring Boot 3.1.11

## ๐Ÿ—‚๏ธ 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)

@@ -177,7 +178,7 @@ ex) [#32] Feat: ์†Œ์…œ ๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ
``` -โ”œโ”€โ”€ .ebextensions-dev +โ”œโ”€โ”€ .ebextensions โ”‚ โ”œโ”€โ”€ 00-set-timezone.config โ”‚ โ”œโ”€โ”€ 01-set-swapmemory.config โ”‚ โ””โ”€โ”€ 02-rabbitmq.config @@ -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) | | :----------------------------------------: | :----------------------------------------: | :----------------------------------------: | :----------------------------------------: | :----------------------------------------: | -|
- Team Leader - | | | | | +|
- Team Leader - | | | | | | Project Manager,
DevOps Engineer,
BE Developer | Backend Developer | Frontend Developer | Frontend Developer | Designer | diff --git a/src/main/java/com/sajang/devracebackend/repository/UserRoomBatchRepository.java b/src/main/java/com/sajang/devracebackend/repository/UserRoomBatchRepository.java index 6a0d996..fa326ba 100644 --- a/src/main/java/com/sajang/devracebackend/repository/UserRoomBatchRepository.java +++ b/src/main/java/com/sajang/devracebackend/repository/UserRoomBatchRepository.java @@ -41,4 +41,22 @@ public int getBatchSize() { } }); } + + public void batchDelete(List 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(); + } + }); + } } diff --git a/src/main/java/com/sajang/devracebackend/service/impl/AuthServiceImpl.java b/src/main/java/com/sajang/devracebackend/service/impl/AuthServiceImpl.java index 628ad0c..8f2e71f 100644 --- a/src/main/java/com/sajang/devracebackend/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/sajang/devracebackend/service/impl/AuthServiceImpl.java @@ -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; @@ -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; @@ -121,10 +123,12 @@ public TokenDto reissue(ReissueRequestDto reissueRequestDto) { // Refresh Token @Override public void withdrawal() { User user = userService.findLoginUser(); + List userRoomList = user.getUserRoomList(); // ์ž์‹ UserRoom ์‚ญ์ œ - hard delete - List 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(); diff --git a/src/main/java/com/sajang/devracebackend/service/impl/UserRoomServiceImpl.java b/src/main/java/com/sajang/devracebackend/service/impl/UserRoomServiceImpl.java index 74083be..914f86c 100644 --- a/src/main/java/com/sajang/devracebackend/service/impl/UserRoomServiceImpl.java +++ b/src/main/java/com/sajang/devracebackend/service/impl/UserRoomServiceImpl.java @@ -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); diff --git a/src/test/java/com/sajang/devracebackend/service/AuthServiceTest.java b/src/test/java/com/sajang/devracebackend/service/AuthServiceTest.java new file mode 100644 index 0000000..6a146ce --- /dev/null +++ b/src/test/java/com/sajang/devracebackend/service/AuthServiceTest.java @@ -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 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 makeFakeUserRooms(Long userId, Long roomId, int userRoomsCount) { // UserRooms ๋”๋ฏธ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + User user = userRepository.findById(userId).orElseThrow(); + Room room = roomRepository.findById(roomId).orElseThrow(); + + List userRoomList = new ArrayList<>(); + for(int i=0; i userList = makeFakeUsers(inputUsersCount); userRepository.saveAll(userList); @@ -56,38 +62,38 @@ void usersEnterRoom_Test() { // Room์˜ waiting ์—…๋ฐ์ดํŠธ String waiting = userIdsString; - Long roomId = 5L; // room_id=5์ธ Room ์—”ํ‹ฐํ‹ฐ String sql = "UPDATE room SET waiting = ? WHERE room_id = ?"; jdbcTemplate.update(sql, waiting, roomId); - // ๋‹ค์ค‘ ์‚ฌ์šฉ์ž ๋™์‹œ์ž…์žฅ + // waiting(์ž…์žฅ ๋Œ€๊ธฐ์—ด)์˜ '๋‹ค์ค‘ ์‚ฌ์šฉ์ž ๋™์‹œ์ž…์žฅ' -> UserRooms ๋”๋ฏธ๋ฐ์ดํ„ฐ ์ƒ์„ฑ LocalDateTime startTime = LocalDateTime.now(); // ์‹œ์ž‘์‹œ๊ฐ ๊ธฐ๋ก userRoomService.usersEnterRoom(roomId); LocalDateTime endTime = LocalDateTime.now(); // ์ข…๋ฃŒ์‹œ๊ฐ ๊ธฐ๋ก // ์ƒ์„ฑํ•œ UserRooms ์‚ญ์ œ (์ž์‹ ์—”ํ‹ฐํ‹ฐ) List userRoomList = roomRepository.findById(roomId).orElseThrow().getUserRoomList(); - userRoomRepository.deleteAll(userRoomList); + deleteFakeUserRooms(userRoomList); + Integer endUserRoomsCount = userRoomRepository.findAll().size(); // ์‚ญ์ œํ›„ UserRooms ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜ ์ธก์ • // ์ƒ์„ฑํ•œ Users ์‚ญ์ œ (๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ) deleteFakeUsers(userList); Integer endUsersCount = userRepository.findAll().size(); // ์‚ญ์ œํ›„ User ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜ ์ธก์ • // ์‹คํ–‰์‹œ๊ฐ„ ์ถœ๋ ฅ - Duration duration = Duration.between(startTime, endTime); // ์ž…์žฅ ์„œ๋น„์Šค ๋ฉ”์†Œ๋“œ ์‹คํ–‰์‹œ๊ฐ„ ์ธก์ • - long milliseconds = duration.toMillis(); // ๋ฐ€๋ฆฌ์ดˆ(ms) - double seconds = milliseconds / 1000.0; // ๋ฐ€๋ฆฌ์ดˆ๋ฅผ ์ดˆ๋กœ ๋ณ€ํ™˜(s) + String printTime = getPrintTime(startTime, endTime); System.out.println("\n< JDBC Batch Insert ์‚ฌ์šฉ (JPA saveAll X) >"); - System.out.println(String.format("- %d๋ช… ๋™์‹œ์ž…์žฅ ์‹คํ–‰์‹œ๊ฐ„: %dms (%.2fs)\n", inputUsersCount, milliseconds, seconds)); // ์ถœ๋ ฅ + System.out.println(String.format("- %d๋ช… ๋™์‹œ์ž…์žฅ ์‹คํ–‰์‹œ๊ฐ„:", inputUsersCount) + printTime); // ์ถœ๋ ฅ + // DB ๋กค๋ฐฑ ๊ฒ€์ฆ + assertThat(startUserRoomsCount).isEqualTo(endUserRoomsCount); // UserRooms ๋”๋ฏธ๋ฐ์ดํ„ฐ ์‚ญ์ œ ๊ฒ€์ฆ assertThat(startUsersCount).isEqualTo(endUsersCount); // Users ๋”๋ฏธ๋ฐ์ดํ„ฐ ์‚ญ์ œ ๊ฒ€์ฆ } // ========== ์œ ํ‹ธ์„ฑ ๋ฉ”์†Œ๋“œ ========== // public List makeFakeUsers(int usersCount) { // Users ๋”๋ฏธ๋ฐ์ดํ„ฐ ์ƒ์„ฑ - List userList = new ArrayList<>(); Long fakeNum = 1L; + List userList = new ArrayList<>(); for(int i=0; i makeFakeUsers(int usersCount) { // Users ๋”๋ฏธ๋ฐ์ดํ„ฐ ์ƒ .build(); userList.add(user); } - return userList; } public void deleteFakeUsers(List userList) { // Users ๋”๋ฏธ๋ฐ์ดํ„ฐ ์‚ญ์ œ userRepository.deleteAll(userList); } + + public void deleteFakeUserRooms(List userRoomList) { // UserRooms ๋”๋ฏธ๋ฐ์ดํ„ฐ ์‚ญ์ œ + userRoomBatchRepository.batchDelete(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; + } }