From 2624754a85bb31d30fd7227b93cf568f16573a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=EC=A3=BC=EC=84=B1?= <99165624+JoosungKwon@users.noreply.github.com> Date: Fri, 4 Aug 2023 23:27:17 +0900 Subject: [PATCH] =?UTF-8?q?[#235]=20=EA=B3=B5=EA=B0=84=20=EC=9D=B8?= =?UTF-8?q?=EB=8D=B1=EC=8A=A4=20=EB=B0=8F=20=EC=84=B8=EC=BB=A8=EB=8D=94?= =?UTF-8?q?=EB=A6=AC=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?(#238)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(P6spy): 정책에 따른 P6Spy 관련 설정 수정 * refactor(DB): SRID 수정 - SRID 를 0에서 5179로 변경 - 공간 인덱스를 설정하기 위함 * refactor(DB): 공간 인덱스 및 세컨더리 인덱스 추가 - 공간 인덱스 적용으로 성능 1만개 데이터 기준 6배 향상 - 유저 세컨더리 인덱스 적용으로 성능 1만개 기준 3배 향상 * feat: GeometryUtils 추가 - 동일한 SRID를 적용하기 위해 * feat: SRID 변경 코드 적용 및 인덱스 쿼리 추가 * feat: 데이터 제너레이터 및 로더 추가 - 데이터를 손쉽게 추가하기 위함 * fix: 유니크 제약으로 인한 테스트 수정 * fix: 유니크 제약으로 인한 테스트 수정 --- build.gradle | 1 + .../domain/crew/api/CrewController.java | 2 +- .../crew/repository/CrewRepository.java | 12 +- .../domain/crew/service/CrewService.java | 2 + .../domain/crew/service/CrewServiceImpl.java | 22 +- .../domain/store/mapper/StoreMapper.java | 10 +- .../store/repository/StoreRepository.java | 4 + .../user/repository/UserRepository.java | 3 + .../global/config/p6spy/P6spyConfig.java | 48 +-- .../config/p6spy/P6spyMessageFormatter.java | 59 ++++ .../global/utils/GeometryUtils.java | 31 ++ .../global/utils/data/DataLoader.java | 282 ++++++++++++++++++ .../global/utils/data/InsertQuery.java | 43 +++ src/main/resources/application-db.yml | 18 +- src/main/resources/application-test.yml | 9 +- src/main/resources/application.yml | 6 - .../db/migration/V5.1__set_spatial_Index.sql | 2 + .../db/migration/V5.2__add_Index.sql | 9 + .../resources/db/migration/V5__chage_SRID.sql | 24 ++ src/main/resources/spy.properties | 1 + .../crew/repository/CrewRepositoryTest.java | 6 +- .../crew/service/CrewServiceImplTest.java | 2 +- .../mukvengers/utils/CrewObjectProvider.java | 3 +- .../mukvengers/utils/StoreObjectProvider.java | 3 +- .../mukvengers/utils/TestDataGenerator.java | 67 +++++ .../mukvengers/utils/UserObjectProvider.java | 8 +- src/test/resources/schema.sql | 27 +- src/test/spy.properties | 1 + 28 files changed, 607 insertions(+), 98 deletions(-) create mode 100644 src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyMessageFormatter.java create mode 100644 src/main/java/com/prgrms/mukvengers/global/utils/GeometryUtils.java create mode 100644 src/main/java/com/prgrms/mukvengers/global/utils/data/DataLoader.java create mode 100644 src/main/java/com/prgrms/mukvengers/global/utils/data/InsertQuery.java create mode 100644 src/main/resources/db/migration/V5.1__set_spatial_Index.sql create mode 100644 src/main/resources/db/migration/V5.2__add_Index.sql create mode 100644 src/main/resources/db/migration/V5__chage_SRID.sql create mode 100644 src/main/resources/spy.properties create mode 100644 src/test/java/com/prgrms/mukvengers/utils/TestDataGenerator.java create mode 100644 src/test/spy.properties diff --git a/build.gradle b/build.gradle index eb35f8aa..4db36441 100644 --- a/build.gradle +++ b/build.gradle @@ -198,6 +198,7 @@ jacocoTestCoverageVerification { } excludes = [ + '*.utils.*', '*.dto.*', '*.exception.*', '*.scheduler.*', diff --git a/src/main/java/com/prgrms/mukvengers/domain/crew/api/CrewController.java b/src/main/java/com/prgrms/mukvengers/domain/crew/api/CrewController.java index e3a2d48f..6d7175fb 100644 --- a/src/main/java/com/prgrms/mukvengers/domain/crew/api/CrewController.java +++ b/src/main/java/com/prgrms/mukvengers/domain/crew/api/CrewController.java @@ -126,7 +126,7 @@ public ResponseEntity> getByUserId( ( @ModelAttribute @Valid SearchCrewRequest distanceRequest ) { - CrewLocationResponses responses = crewService.getByLocation(distanceRequest); + CrewLocationResponses responses = crewService.getByLocationWithIndex(distanceRequest); return ResponseEntity.ok().body(new ApiResponse<>(responses)); } diff --git a/src/main/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepository.java b/src/main/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepository.java index 67d72b78..a3f9cbba 100644 --- a/src/main/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepository.java +++ b/src/main/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepository.java @@ -27,9 +27,17 @@ public interface CrewRepository extends JpaRepository { @Query(nativeQuery = true, value = "SELECT * FROM crew c " - + "WHERE ST_DISTANCE_SPHERE(:location, c.location) < :distance AND c.status = 'RECRUITING'") + + "WHERE ST_DISTANCE_SPHERE(:location, c.location) < :distance AND c.status = 'RECRUITING'") List findAllByLocation(@Param("location") Point location, @Param("distance") int distance); + @Query(nativeQuery = true, value = """ + SELECT * FROM crew c + WHERE ST_Contains( + ST_Buffer(:location, :radius), c.location) + AND c.status = 'RECRUITING' + """) + List findAllByLocation(@Param("location") Point location, @Param("radius") Double radius); + @Modifying @Query(value = """ UPDATE Crew c @@ -38,4 +46,6 @@ public interface CrewRepository extends JpaRepository { """) int updateAllStatusToFinish(@Param("time") LocalDateTime now); + @Query(value = "SELECT * FROM crew c ORDER BY RAND() LIMIT :count", nativeQuery = true) + List findRandom(@Param("count") int count); } diff --git a/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewService.java b/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewService.java index c92f105d..7b7f848a 100644 --- a/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewService.java +++ b/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewService.java @@ -25,5 +25,7 @@ public interface CrewService { CrewLocationResponses getByLocation(SearchCrewRequest distanceRequest); + CrewLocationResponses getByLocationWithIndex(SearchCrewRequest distanceRequest); + CrewStatusResponse updateStatus(Long crewId, Long userId, CrewStatus crewStatus); } diff --git a/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImpl.java b/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImpl.java index de92518f..875a568e 100644 --- a/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImpl.java +++ b/src/main/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImpl.java @@ -44,6 +44,7 @@ import com.prgrms.mukvengers.domain.user.model.User; import com.prgrms.mukvengers.domain.user.repository.UserRepository; import com.prgrms.mukvengers.global.common.dto.IdResponse; +import com.prgrms.mukvengers.global.utils.GeometryUtils; import lombok.RequiredArgsConstructor; @@ -60,6 +61,7 @@ public class CrewServiceImpl implements CrewService { private final CrewMapper crewMapper; private final StoreMapper storeMapper; private final CrewMemberMapper crewMemberMapper; + private final GeometryFactory gf = GeometryUtils.getInstance(); @Override @Transactional @@ -157,8 +159,6 @@ public CrewPageResponse getByPlaceId(Long userId, String placeId, Pageable pagea @Override public CrewLocationResponses getByLocation(SearchCrewRequest distanceRequest) { - GeometryFactory gf = new GeometryFactory(); - Point location = gf.createPoint(new Coordinate(distanceRequest.longitude(), distanceRequest.latitude())); List responses = crewRepository.findAllByLocation(location, distanceRequest.distance()) @@ -170,6 +170,23 @@ public CrewLocationResponses getByLocation(SearchCrewRequest distanceRequest) { return new CrewLocationResponses(responses); } + @Override + public CrewLocationResponses getByLocationWithIndex(SearchCrewRequest distanceRequest) { + + Coordinate coordinate = new Coordinate(distanceRequest.longitude(), distanceRequest.latitude()); + + Point location = gf.createPoint(coordinate); + Double radius = GeometryUtils.calculateApproximateRadius(distanceRequest.distance()); + + List responses = crewRepository.findAllByLocation(location, radius) + .stream() + .map(crew -> crewMapper.toCrewLocationResponse(crew.getLocation(), crew.getStore().getId(), + crew.getStore().getPlaceName())) + .toList(); + + return new CrewLocationResponses(responses); + } + @Override @Transactional public CrewStatusResponse updateStatus(Long crewId, Long userId, CrewStatus crewStatus) { @@ -218,4 +235,5 @@ public CrewStatusResponse updateStatus(Long crewId, Long userId, CrewStatus crew return new CrewStatusResponse(crew.getStatus()); } + } diff --git a/src/main/java/com/prgrms/mukvengers/domain/store/mapper/StoreMapper.java b/src/main/java/com/prgrms/mukvengers/domain/store/mapper/StoreMapper.java index 587589ae..72bca3ad 100644 --- a/src/main/java/com/prgrms/mukvengers/domain/store/mapper/StoreMapper.java +++ b/src/main/java/com/prgrms/mukvengers/domain/store/mapper/StoreMapper.java @@ -10,6 +10,7 @@ import com.prgrms.mukvengers.domain.store.dto.request.CreateStoreRequest; import com.prgrms.mukvengers.domain.store.dto.response.StoreResponse; import com.prgrms.mukvengers.domain.store.model.Store; +import com.prgrms.mukvengers.global.utils.GeometryUtils; @Mapper(componentModel = "spring") public interface StoreMapper { @@ -33,13 +34,10 @@ default Double mapLatitude(Store store) { @Named("pointMethod") default Point mapPoint(CreateStoreRequest request) { + GeometryFactory gf = GeometryUtils.getInstance(); + Coordinate coordinate = new Coordinate(request.longitude(), request.latitude()); - GeometryFactory gf = new GeometryFactory(); - - return gf.createPoint(new Coordinate( - request.longitude(), - request.latitude())); - + return gf.createPoint(coordinate); } } diff --git a/src/main/java/com/prgrms/mukvengers/domain/store/repository/StoreRepository.java b/src/main/java/com/prgrms/mukvengers/domain/store/repository/StoreRepository.java index 56a183e3..575b99b2 100644 --- a/src/main/java/com/prgrms/mukvengers/domain/store/repository/StoreRepository.java +++ b/src/main/java/com/prgrms/mukvengers/domain/store/repository/StoreRepository.java @@ -1,8 +1,10 @@ package com.prgrms.mukvengers.domain.store.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.prgrms.mukvengers.domain.store.model.Store; @@ -11,4 +13,6 @@ public interface StoreRepository extends JpaRepository { Optional findByPlaceId(@Param("placeId") String placeId); + @Query(value = "SELECT * FROM store s ORDER BY RAND() LIMIT :count", nativeQuery = true) + List findRandom(@Param("count") int count); } diff --git a/src/main/java/com/prgrms/mukvengers/domain/user/repository/UserRepository.java b/src/main/java/com/prgrms/mukvengers/domain/user/repository/UserRepository.java index 30b2248e..f29e4cf8 100644 --- a/src/main/java/com/prgrms/mukvengers/domain/user/repository/UserRepository.java +++ b/src/main/java/com/prgrms/mukvengers/domain/user/repository/UserRepository.java @@ -1,5 +1,6 @@ package com.prgrms.mukvengers.domain.user.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -20,4 +21,6 @@ Optional findByUserIdByProviderAndOauthId( @Param("provider") String provider, @Param("oauthId") String oauthId); + @Query(value = "SELECT * FROM users u ORDER BY RAND() LIMIT :count", nativeQuery = true) + List findRandom(@Param("count") int count); } diff --git a/src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyConfig.java b/src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyConfig.java index b21c697a..950fe216 100644 --- a/src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyConfig.java +++ b/src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyConfig.java @@ -1,60 +1,16 @@ package com.prgrms.mukvengers.global.config.p6spy; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.Locale; - import javax.annotation.PostConstruct; -import org.hibernate.engine.jdbc.internal.FormatStyle; import org.springframework.context.annotation.Configuration; -import com.p6spy.engine.logging.Category; import com.p6spy.engine.spy.P6SpyOptions; -import com.p6spy.engine.spy.appender.MessageFormattingStrategy; @Configuration -public class P6spyConfig implements MessageFormattingStrategy { +public class P6spyConfig { @PostConstruct public void setLogMessageFormat() { - P6SpyOptions.getActiveInstance().setLogMessageFormat(this.getClass().getName()); - } - - @Override - public String formatMessage(int connectionId, String now, long elapsed, String category, - String prepared, String sql, String url) { - sql = formatSql(category, sql); - Date currentDate = new Date(); - - SimpleDateFormat formatter = new SimpleDateFormat("yy.MM.dd HH:mm:ss"); - - return category + " | " + "OperationTime : " + elapsed + "ms" + sql; - } - - private String formatSql(String category, String sql) { - if (sql == null || sql.isBlank()) { - return sql; - } - - if (Category.STATEMENT.getName().equals(category)) { - String tmpsql = sql.trim().toLowerCase(Locale.ROOT); - if (tmpsql.startsWith("create") || tmpsql.startsWith("alter")) { - sql = FormatStyle.DDL.getFormatter().format(sql); - } else { - sql = FormatStyle.BASIC.getFormatter().format(sql); - } - sql = "\n" + stackTrace() + "\n" + sql + "\n"; - } - - return sql; + P6SpyOptions.getActiveInstance().setLogMessageFormat(P6spyMessageFormatter.class.getName()); } - - private String stackTrace() { - return Arrays.toString(Arrays.stream(new Throwable().getStackTrace()) - .filter(t -> t.toString().startsWith("com.prgrms.mukvengers")) - .toArray()).replace(", ", "\n"); - } - } diff --git a/src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyMessageFormatter.java b/src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyMessageFormatter.java new file mode 100644 index 00000000..52b5d35f --- /dev/null +++ b/src/main/java/com/prgrms/mukvengers/global/config/p6spy/P6spyMessageFormatter.java @@ -0,0 +1,59 @@ +package com.prgrms.mukvengers.global.config.p6spy; + +import java.util.Arrays; +import java.util.Locale; + +import org.hibernate.engine.jdbc.internal.FormatStyle; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import com.p6spy.engine.logging.Category; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; + +@Profile("default") +@Configuration +public class P6spyMessageFormatter implements MessageFormattingStrategy { + + @Override + public String formatMessage(int connectionId, String now, long elapsed, String category, + String prepared, String sql, String url) { + + sql = formatSql(category, sql); + return category + " | " + "OperationTime : " + elapsed + "ms" + sql; + } + + private String formatSql(String category, String sql) { + if (sql == null || sql.isBlank()) { + return sql; + } + + if (Category.STATEMENT.getName().equals(category)) { + String tmpsql = sql.trim().toLowerCase(Locale.ROOT); + if (tmpsql.startsWith("create") || tmpsql.startsWith("alter")) { + sql = FormatStyle.DDL.getFormatter().format(sql); + } else { + sql = FormatStyle.BASIC.getFormatter().format(sql); + } + } + + sql += "\n"; + + String[] stackTrace = stackTrace(); + + if (stackTrace.length > 0) { + sql += Arrays.toString(stackTrace).replace(", ", "\n"); + } + + return sql; + } + + private String[] stackTrace() { + return Arrays.stream(new Throwable().getStackTrace()) + .map(StackTraceElement::toString) + .filter(string -> string.startsWith("com.prgrms.mukvengers") + && !string.startsWith("com.prgrms.mukvengers.global.config.p6spy") + && !string.startsWith("com.prgrms.mukvengers.MukvengersApplication.main")) + .toArray(String[]::new); + } + +} \ No newline at end of file diff --git a/src/main/java/com/prgrms/mukvengers/global/utils/GeometryUtils.java b/src/main/java/com/prgrms/mukvengers/global/utils/GeometryUtils.java new file mode 100644 index 00000000..7ac8326b --- /dev/null +++ b/src/main/java/com/prgrms/mukvengers/global/utils/GeometryUtils.java @@ -0,0 +1,31 @@ +package com.prgrms.mukvengers.global.utils; + +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.PrecisionModel; + +public class GeometryUtils { + + private static final GeometryFactory INSTANCE + = new GeometryFactory(new PrecisionModel(), 5179); + + private GeometryUtils() { + /* no-op */ + } + + public static GeometryFactory getInstance() { + return INSTANCE; + } + + // Meter -> radius + public static double calculateApproximateRadius(int distanceInMeters) { + // The approximate radius of the earth in meters + double earthRadius = 6371000; + + // convert distance to radius in radians + double radiusInRadians = distanceInMeters / earthRadius; + + // convert radians to degrees + return Math.toDegrees(radiusInRadians); + } + +} diff --git a/src/main/java/com/prgrms/mukvengers/global/utils/data/DataLoader.java b/src/main/java/com/prgrms/mukvengers/global/utils/data/DataLoader.java new file mode 100644 index 00000000..b40d651b --- /dev/null +++ b/src/main/java/com/prgrms/mukvengers/global/utils/data/DataLoader.java @@ -0,0 +1,282 @@ +package com.prgrms.mukvengers.global.utils.data; + +import static com.prgrms.mukvengers.global.utils.data.InsertQuery.*; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.io.WKTWriter; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import com.prgrms.mukvengers.domain.crew.model.Crew; +import com.prgrms.mukvengers.domain.crew.repository.CrewRepository; +import com.prgrms.mukvengers.domain.crewmember.model.CrewMember; +import com.prgrms.mukvengers.domain.crewmember.model.vo.CrewMemberRole; +import com.prgrms.mukvengers.domain.store.model.Store; +import com.prgrms.mukvengers.domain.store.repository.StoreRepository; +import com.prgrms.mukvengers.domain.user.model.User; +import com.prgrms.mukvengers.domain.user.repository.UserRepository; +import com.prgrms.mukvengers.global.utils.GeometryUtils; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class DataLoader { + + private final JdbcTemplate jdbcTemplate; + + private final DataGenerator dataGenerator = new DataGenerator(); + + public void loadSetCrewAndPeople(int count, StoreRepository storeRepository, + UserRepository userRepository, CrewRepository crewRepository) { + + loadUser(count); + loadCrew(count, storeRepository); + + List userList = userRepository.findRandom(count); + List crewList = crewRepository.findRandom(count); + + int minCount = Math.min(userList.size(), crewList.size()); + + List crewMemberList = dataGenerator.generateCrewMember(minCount, userList, crewList); + + jdbcTemplate.batchUpdate( + CREW_MEMBER_INSERT_SQL.sql(), new BatchPreparedStatementSetter() { + + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + CrewMember crewMember = crewMemberList.get(i); + ps.setLong(1, crewMember.getUserId()); + ps.setLong(2, crewMember.getCrew().getId()); + ps.setString(3, crewMember.getCrewMemberRole().name()); + } + + @Override + public int getBatchSize() { + return crewMemberList.size(); + } + } + ); + + } + + public void loadUser(int count) { + + List userInfoList = dataGenerator.generateUser(count); + + jdbcTemplate.batchUpdate( + USER_INSERT.sql(), new BatchPreparedStatementSetter() { + + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + User userInfo = userInfoList.get(i); + ps.setString(1, userInfo.getNickname()); + ps.setString(2, userInfo.getIntroduction()); + ps.setString(3, userInfo.getProfileImgUrl()); + ps.setString(4, userInfo.getProvider()); + ps.setString(5, userInfo.getOauthId()); + } + + @Override + public int getBatchSize() { + return userInfoList.size(); + } + } + ); + } + + public void loadStore(int count) { + + List storeList = dataGenerator.generateStore(count); + + WKTWriter wktWriter = new WKTWriter(); + + jdbcTemplate.batchUpdate( + STORE_INSERT_SQL.sql(), new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + Store store = storeList.get(i); + ps.setString(1, wktWriter.write(store.getLocation())); + ps.setString(2, store.getPlaceId()); + ps.setString(3, store.getPlaceName()); + ps.setString(4, store.getCategories()); + ps.setString(5, store.getRoadAddressName()); + ps.setString(6, store.getPhotoUrls()); + ps.setString(7, store.getKakaoPlaceUrl()); + ps.setString(8, store.getPhoneNumber()); + } + + @Override + public int getBatchSize() { + return storeList.size(); + } + } + ); + + } + + public void loadCrew(int count, StoreRepository storeRepository) { + + loadStore(count); + List storeList = storeRepository.findRandom(count); + List crewList = dataGenerator.generateCrew(storeList.size(), storeList); + + WKTWriter wktWriter = new WKTWriter(); + + jdbcTemplate.batchUpdate( + CREW_INSERT_SQL.sql(), new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + Crew crew = crewList.get(i); + ps.setLong(1, crew.getStore().getId()); + ps.setString(2, crew.getName()); + ps.setString(3, wktWriter.write(crew.getStore().getLocation())); + ps.setString(4, crew.getStatus().name()); + ps.setTimestamp(5, Timestamp.valueOf(crew.getPromiseTime())); + ps.setString(6, crew.getContent()); + ps.setString(7, crew.getCategory()); + } + + @Override + public int getBatchSize() { + return crewList.size(); + } + } + ); + } + + public static class DataGenerator { + + private static final Random random = new Random(); + // Store + private static final String placeId = ""; + private static final String DEFAULT_STORE_PLACE_NAME = "테스트 음식점"; + private static final String STORE_CATEGORIES = "음식점 > 한식"; + private static final String STORE_ROAD_ADDRESS_NAME = "강남구 테스트로"; + private static final String STORE_PHOTO_URLS = "'https://~', 'https://~'"; + private static final String STORE_DEFAULT_KAKAO_PLACE_URL = "http://place.map.kakao.com/"; + private static final String STORE_PHONE_NUMBER = "010-1234-5678"; + // Crew + private static final String NAME = "원정대"; + private static final Integer CAPACITY = 2; + private static final String CONTENT = "저는 백엔드 개발자 입니다. 프론트 엔드 개발자 구해요"; + private static final String CATEGORY = "조용한"; + private static final LocalDateTime PROMISE_TIME = LocalDateTime.now(); + + DataGenerator() { + } + + List generateUser(int count) { + + List userInfoList = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + + long randomNum = (random.nextLong(count) * i) + i; + + User userInfo = User.builder() + .nickname("TEST : " + i + "-" + randomNum) + .profileImgUrl("Default Profile Image URL") + .provider("TEST") + .oauthId(String.valueOf(randomNum)) + .build(); + + userInfoList.add(userInfo); + } + + return userInfoList; + } + + List generateStore(int count) { + GeometryFactory gf = GeometryUtils.getInstance(); + + List storeList = new ArrayList<>(); + for (int i = 0; i < count; i++) { + // 랜덤 위치 생성하기 - 서울 위경도 범위 내에서 + double minLon = 126.7643; + double maxLon = 127.1833; + double minLat = 37.4269; + double maxLat = 37.7017; + + Random rand = new Random(); + + double longitude = minLon + (maxLon - minLon) * rand.nextDouble(); + double latitude = minLat + (maxLat - minLat) * rand.nextDouble(); + + Point location = gf.createPoint(new Coordinate(longitude, latitude)); + + long randomNum = (random.nextLong(count) * i) + i; + + Store store = Store.builder() + .location(location) + .placeId(placeId + randomNum) + .placeName(DEFAULT_STORE_PLACE_NAME + "-" + randomNum) + .categories(STORE_CATEGORIES) + .roadAddressName(STORE_ROAD_ADDRESS_NAME) + .photoUrls(STORE_PHOTO_URLS) + .kakaoPlaceUrl(STORE_DEFAULT_KAKAO_PLACE_URL + randomNum) + .phoneNumber(STORE_PHONE_NUMBER) + .build(); + + storeList.add(store); + } + + return storeList; + } + + List generateCrew(int count, List storeList) { + + List crewList = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + Store store = storeList.get(i); + + Crew crew = Crew.builder() + .store(store) + .name(NAME) + .location(store.getLocation()) + .promiseTime(PROMISE_TIME) + .capacity(CAPACITY) + .content(CONTENT) + .category(CATEGORY) + .build(); + + crewList.add(crew); + } + + return crewList; + } + + // 앞선 모든 과정이 필요함 + List generateCrewMember(int count, List userList, List crewList) { + + List crewMemberList = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + User user = userList.get(i); + Crew crew = crewList.get(i); + + CrewMember crewMember = CrewMember.builder() + .crew(crew) + .crewMemberRole(CrewMemberRole.LEADER) + .userId(user.getId()) + .build(); + + crewMemberList.add(crewMember); + } + + return crewMemberList; + } + } +} diff --git a/src/main/java/com/prgrms/mukvengers/global/utils/data/InsertQuery.java b/src/main/java/com/prgrms/mukvengers/global/utils/data/InsertQuery.java new file mode 100644 index 00000000..2185fdf9 --- /dev/null +++ b/src/main/java/com/prgrms/mukvengers/global/utils/data/InsertQuery.java @@ -0,0 +1,43 @@ +package com.prgrms.mukvengers.global.utils.data; + +public enum InsertQuery { + + USER_INSERT( + """ + INSERT IGNORE INTO users (nickname, introduction, profile_img_url, provider, oauth_id) + VALUES (?, ?, ?, ?, ?) + """ + ), + + STORE_INSERT_SQL( + """ + INSERT IGNORE INTO store (location, place_id, place_name, categories, road_address_name, photo_urls, kakao_place_url, phone_number) + VALUES (ST_GeomFromText(?, 5179), ?, ?, ?, ?, ?, ?, ?) + """ + ), + + CREW_INSERT_SQL( + """ + INSERT IGNORE INTO crew (store_id, name, location, status, promise_time, content, category) + VALUES (?, ?, ST_GeomFromText(?, 5179), ?, ?, ?, ?) + """ + ), + + CREW_MEMBER_INSERT_SQL( + """ + INSERT IGNORE INTO crew_member (user_id, crew_id, crew_member_role) + VALUES (?, ?, ?) + """ + ); + + private final String sql; + + InsertQuery(String sql) { + this.sql = sql; + } + + public String sql() { + return sql; + } + +} diff --git a/src/main/resources/application-db.yml b/src/main/resources/application-db.yml index ebefd89d..f1609697 100644 --- a/src/main/resources/application-db.yml +++ b/src/main/resources/application-db.yml @@ -28,8 +28,7 @@ spring: properties: hibernate: - dialect: org.hibernate.spatial.dialect.mysql.MySQL8SpatialDialect - format_sql: true + dialect: org.hibernate.spatial.dialect.mysql.MySQLSpatialDialect default_batch_fetch_size: 1000 # flyway 설정 @@ -42,13 +41,8 @@ spring: host: ${REDIS_HOST} port: ${REDIS_PORT} ---- # local -spring: - config: - activate.on-profile: local - -# SQL 로그 설정 -logging: - level: - org.hibernate.SQL: debug - org.hibernate.type.descriptor.sql: trace \ No newline at end of file +# P6spy 설정 +decorator: + datasource: + p6spy: + enable-logging: true \ No newline at end of file diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 710fcfd5..0da51a48 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -20,7 +20,7 @@ spring: ddl-auto: none properties: hibernate: - dialect: org.hibernate.spatial.dialect.mysql.MySQL8SpatialDialect + dialect: org.hibernate.spatial.dialect.mysql.MySQLSpatialDialect flyway: enabled: false @@ -43,9 +43,4 @@ front: decorator: datasource: p6spy: - enable-logging: true - -logging: - level: - org.hibernate.SQL: debug - org.hibernate.type.descriptor.sql: trace \ No newline at end of file + enable-logging: true \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b28b6800..c461e1b3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -49,12 +49,6 @@ management: exposure: include: '*' -# P6spy 설정 -decorator: - datasource: - p6spy: - enable-logging: true - # sentry 설정 sentry: dsn: ${SENTRY_DSN} diff --git a/src/main/resources/db/migration/V5.1__set_spatial_Index.sql b/src/main/resources/db/migration/V5.1__set_spatial_Index.sql new file mode 100644 index 00000000..964a911e --- /dev/null +++ b/src/main/resources/db/migration/V5.1__set_spatial_Index.sql @@ -0,0 +1,2 @@ +-- 밥 모임 위치 정보에 설정 +CREATE SPATIAL INDEX spatial_index_in_crew ON crew (location); \ No newline at end of file diff --git a/src/main/resources/db/migration/V5.2__add_Index.sql b/src/main/resources/db/migration/V5.2__add_Index.sql new file mode 100644 index 00000000..8a72b2be --- /dev/null +++ b/src/main/resources/db/migration/V5.2__add_Index.sql @@ -0,0 +1,9 @@ +-- 밥 모임 상태 정보에 설정 +ALTER TABLE crew + ADD INDEX index_in_status (status); + +ALTER TABLE proposal + ADD INDEX index_in_leader_id (leader_id); + +ALTER TABLE users + ADD UNIQUE INDEX index_in_provider_and_oauth_id (oauth_id, provider); \ No newline at end of file diff --git a/src/main/resources/db/migration/V5__chage_SRID.sql b/src/main/resources/db/migration/V5__chage_SRID.sql new file mode 100644 index 00000000..92a522bc --- /dev/null +++ b/src/main/resources/db/migration/V5__chage_SRID.sql @@ -0,0 +1,24 @@ +ALTER TABLE store + ADD location_new_srid POINT SRID 5179 NOT NULL; + +UPDATE store +SET location_new_srid = ST_PointFromText(ST_AsText(location), 5179); + +ALTER TABLE store + DROP location; + +ALTER TABLE store + CHANGE location_new_srid location POINT SRID 5179 NOT NULL; + + +ALTER TABLE crew + ADD location_new_srid POINT SRID 5179 NOT NULL; + +UPDATE crew +SET location_new_srid = ST_PointFromText(ST_AsText(location), 5179); + +ALTER TABLE crew + DROP location; + +ALTER TABLE crew + CHANGE location_new_srid location POINT SRID 5179 NOT NULL; \ No newline at end of file diff --git a/src/main/resources/spy.properties b/src/main/resources/spy.properties new file mode 100644 index 00000000..98cbe0a9 --- /dev/null +++ b/src/main/resources/spy.properties @@ -0,0 +1 @@ +logMessageFormat=com.prgrms.mukvengers.global.config.p6spy.P6spyMessageFormatter diff --git a/src/test/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepositoryTest.java b/src/test/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepositoryTest.java index d547125a..1ed8764d 100644 --- a/src/test/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepositoryTest.java +++ b/src/test/java/com/prgrms/mukvengers/domain/crew/repository/CrewRepositoryTest.java @@ -16,6 +16,7 @@ import com.prgrms.mukvengers.base.RepositoryTest; import com.prgrms.mukvengers.domain.crew.model.Crew; +import com.prgrms.mukvengers.global.utils.GeometryUtils; class CrewRepositoryTest extends RepositoryTest { @@ -38,11 +39,12 @@ void joinStoreByPlaceId_success() { @DisplayName("[성공] 현재 사용자의 위도와 경도, 그리고 거리를 받아 거리 안에 있는 밥 모임을 조회한다.") void findAllByDistance_success() { - GeometryFactory gf = new GeometryFactory(); + GeometryFactory gf = GeometryUtils.getInstance(); double longitude = -147.4654321321; double latitude = 35.75413579; Point location = gf.createPoint(new Coordinate(longitude, latitude)); - List savedCrews = crewRepository.findAllByLocation(location, 1000); + Double radius = GeometryUtils.calculateApproximateRadius(1000); + List savedCrews = crewRepository.findAllByLocation(location, radius); assertThat(savedCrews).hasSize(crews.size()); diff --git a/src/test/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImplTest.java b/src/test/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImplTest.java index da28404f..6989fab5 100644 --- a/src/test/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImplTest.java +++ b/src/test/java/com/prgrms/mukvengers/domain/crew/service/CrewServiceImplTest.java @@ -162,7 +162,7 @@ void findByLocation_success() { SearchCrewRequest distanceRequest = new SearchCrewRequest(longitude, latitude, distance); //when - CrewLocationResponses crewLocationResponse = crewService.getByLocation(distanceRequest); + CrewLocationResponses crewLocationResponse = crewService.getByLocationWithIndex(distanceRequest); //then List responses = crewLocationResponse.responses(); diff --git a/src/test/java/com/prgrms/mukvengers/utils/CrewObjectProvider.java b/src/test/java/com/prgrms/mukvengers/utils/CrewObjectProvider.java index 8d82d876..96c1030d 100644 --- a/src/test/java/com/prgrms/mukvengers/utils/CrewObjectProvider.java +++ b/src/test/java/com/prgrms/mukvengers/utils/CrewObjectProvider.java @@ -16,10 +16,11 @@ import com.prgrms.mukvengers.domain.crew.model.Crew; import com.prgrms.mukvengers.domain.crew.model.vo.CrewStatus; import com.prgrms.mukvengers.domain.store.model.Store; +import com.prgrms.mukvengers.global.utils.GeometryUtils; public class CrewObjectProvider { - private final static GeometryFactory GF = new GeometryFactory(); + private final static GeometryFactory GF = GeometryUtils.getInstance(); private static final String LATITUDE = "35.75413579"; private static final String LONGITUDE = "-147.4654321321"; private static final String NAME = "원정대이름"; diff --git a/src/test/java/com/prgrms/mukvengers/utils/StoreObjectProvider.java b/src/test/java/com/prgrms/mukvengers/utils/StoreObjectProvider.java index cf6ff961..d2727175 100644 --- a/src/test/java/com/prgrms/mukvengers/utils/StoreObjectProvider.java +++ b/src/test/java/com/prgrms/mukvengers/utils/StoreObjectProvider.java @@ -6,10 +6,11 @@ import com.prgrms.mukvengers.domain.store.dto.request.CreateStoreRequest; import com.prgrms.mukvengers.domain.store.model.Store; +import com.prgrms.mukvengers.global.utils.GeometryUtils; public class StoreObjectProvider { - private final static GeometryFactory STORE_GF = new GeometryFactory(); + private final static GeometryFactory STORE_GF = GeometryUtils.getInstance(); private final static double STORE_LONGITUDE = -147.4654321321; private final static double STORE_LATITUDE = 35.75413579; private final static String STORE_PLACE_ID = "16618597"; diff --git a/src/test/java/com/prgrms/mukvengers/utils/TestDataGenerator.java b/src/test/java/com/prgrms/mukvengers/utils/TestDataGenerator.java new file mode 100644 index 00000000..82c90e8a --- /dev/null +++ b/src/test/java/com/prgrms/mukvengers/utils/TestDataGenerator.java @@ -0,0 +1,67 @@ +package com.prgrms.mukvengers.utils; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; + +import com.prgrms.mukvengers.domain.crew.repository.CrewRepository; +import com.prgrms.mukvengers.domain.store.repository.StoreRepository; +import com.prgrms.mukvengers.domain.user.repository.UserRepository; +import com.prgrms.mukvengers.global.utils.data.DataLoader; + +@Disabled +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class TestDataGenerator { + + @Autowired + private DataLoader dataLoader; + + @Autowired + private UserRepository userRepository; + + @Autowired + private StoreRepository storeRepository; + + @Autowired + private CrewRepository crewRepository; + + private long start; + private long end; + + @BeforeEach + void setStart() { + start = System.currentTimeMillis(); + } + + @AfterEach + void setEnd() { + end = System.currentTimeMillis(); + System.out.println("걸린시간 : " + (end - start) / 1000.0 + "초"); + } + + @Test + void insertUserTest() { + dataLoader.loadUser(10000); + } + + @Test + void insertStoreTest() { + dataLoader.loadStore(1000); + } + + @Test + void insertCrewTest() {// 가게 저장 로직도 포함되어 있음! + dataLoader.loadCrew(1000, storeRepository); + } + + @Test + void insertDefaultSetting() { + dataLoader.loadSetCrewAndPeople(100, storeRepository, userRepository, crewRepository); + } + +} diff --git a/src/test/java/com/prgrms/mukvengers/utils/UserObjectProvider.java b/src/test/java/com/prgrms/mukvengers/utils/UserObjectProvider.java index fc5143ed..9386fdc5 100644 --- a/src/test/java/com/prgrms/mukvengers/utils/UserObjectProvider.java +++ b/src/test/java/com/prgrms/mukvengers/utils/UserObjectProvider.java @@ -1,5 +1,7 @@ package com.prgrms.mukvengers.utils; +import java.util.Random; + import com.prgrms.mukvengers.domain.user.dto.request.UpdateUserRequest; import com.prgrms.mukvengers.domain.user.model.User; @@ -10,13 +12,17 @@ public class UserObjectProvider { public static final String DEFAULT_PROFILE_IMG_URL = "https://defaultImg.jpg"; public static final String PROVIDER_KAKAO = "kakao"; public static final String OAUTH_ID = "12345"; + private static final Random rand = new Random(); public static User createUser() { + + int num = rand.nextInt(10000) + 1; + return User.builder() .nickname(DEFAULT_NICKNAME) .profileImgUrl(DEFAULT_PROFILE_IMG_URL) .provider(PROVIDER_KAKAO) - .oauthId(OAUTH_ID) + .oauthId(String.valueOf(num)) .build(); } diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql index c535ea52..b728e2ec 100644 --- a/src/test/resources/schema.sql +++ b/src/test/resources/schema.sql @@ -30,13 +30,15 @@ CREATE TABLE users oauth_id varchar(255) NOT NULL, created_at dateTime NOT NULL DEFAULT now(), updated_at dateTime NOT NULL DEFAULT now(), - deleted boolean NOT NULL DEFAULT false + deleted boolean NOT NULL DEFAULT false, + + UNIQUE index_in_provider_and_oauth_id (oauth_id, provider) ); CREATE TABLE store ( id bigint NOT NULL PRIMARY KEY AUTO_INCREMENT, - location point NOT NULL, + location point NOT NULL SRID 5179, place_id varchar(255) NOT NULL, place_name varchar(255) NULL, categories varchar(255) NULL, @@ -56,7 +58,7 @@ CREATE TABLE crew id bigint NOT NULL PRIMARY KEY AUTO_INCREMENT, store_id bigint NOT NULL, name varchar(20) NOT NULL, - location point NOT NULL, + location point NOT NULL SRID 5179, capacity int NOT NULL DEFAULT 2, status varchar(255) NOT NULL, promise_time dateTime NOT NULL, @@ -66,7 +68,9 @@ CREATE TABLE crew updated_at dateTime NOT NULL DEFAULT now(), deleted boolean NOT NULL DEFAULT false, - FOREIGN KEY fk_crew_store_id (store_id) REFERENCES store (id) + FOREIGN KEY fk_crew_store_id (store_id) REFERENCES store (id), + SPATIAL INDEX spatial_index_in_crew (location), + INDEX index_in_status (status) ); CREATE TABLE crew_member @@ -114,7 +118,8 @@ CREATE TABLE proposal deleted boolean NOT NULL DEFAULT false, FOREIGN KEY fk_proposal_user_id (user_id) REFERENCES users (id), - FOREIGN KEY fk_proposal_crew_id (crew_id) REFERENCES crew (id) + FOREIGN KEY fk_proposal_crew_id (crew_id) REFERENCES crew (id), + INDEX index_in_leader_id (leader_id) ); CREATE TABLE chat @@ -126,7 +131,9 @@ CREATE TABLE chat content varchar(255) NOT NULL, created_at dateTime NOT NULL DEFAULT now(), updated_at dateTime NOT NULL DEFAULT now(), - deleted boolean NOT NULL DEFAULT false + deleted boolean NOT NULL DEFAULT false, + + INDEX idx_crew_id (crew_id) ); CREATE TABLE notification @@ -138,10 +145,8 @@ CREATE TABLE notification receiver_id bigint NOT NULL, created_at dateTime NOT NULL DEFAULT now(), updated_at dateTime NOT NULL DEFAULT now(), - deleted boolean NOT NULL DEFAULT false -); + deleted boolean NOT NULL DEFAULT false, -create index idx_crew_id on chat (crew_id); -# receiver_id 인덱스 추가 -create index idx_receiver_id on notification (receiver_id); + INDEX idx_receiver_id (receiver_id) +); diff --git a/src/test/spy.properties b/src/test/spy.properties new file mode 100644 index 00000000..98cbe0a9 --- /dev/null +++ b/src/test/spy.properties @@ -0,0 +1 @@ +logMessageFormat=com.prgrms.mukvengers.global.config.p6spy.P6spyMessageFormatter