Skip to content
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] Feat/#548 공간 인덱스 구현 #584

Merged
merged 12 commits into from
Oct 17, 2023
Merged
6 changes: 4 additions & 2 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation group: 'com.github.maricn', name: 'logback-slack-appender', version: '1.6.1'

implementation 'com.github.maricn:logback-slack-appender:1.6.1'
implementation 'org.hibernate:hibernate-spatial:6.2.5.Final'
implementation 'mysql:mysql-connector-java:8.0.32'

implementation 'io.jsonwebtoken:jjwt:0.9.1'
Expand All @@ -44,6 +44,8 @@ dependencies {
testImplementation 'io.rest-assured:rest-assured'
testImplementation 'io.rest-assured:spring-mock-mvc'
testImplementation 'org.assertj:assertj-core:3.19.0'
testImplementation 'org.testcontainers:mysql:1.17.2'
testImplementation 'org.testcontainers:junit-jupiter:1.17.2'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오홍 testContainer 전용으로 junit 의존성도 추가로 필요한가보군요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예쓰 !! junit에서 제공해주는 것 같더라구요


// S3
implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.1000')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public List<TopicResponse> findNearbyTopicsSortedByPinCount(
) {
Coordinate coordinate = Coordinate.of(latitude, longitude);
List<Location> nearLocation = locationRepository.findAllByCoordinateAndDistanceInMeters(
coordinate,
coordinate.getCoordinate(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

간지나네용

NEAR_DISTANCE_METERS
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package com.mapbefine.mapbefine.location.domain;

import static com.mapbefine.mapbefine.location.exception.LocationErrorCode.ILLEGAL_COORDINATE_RANGE;
import static java.lang.Math.acos;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.toRadians;
import static lombok.AccessLevel.PROTECTED;

import com.mapbefine.mapbefine.location.exception.LocationException.LocationBadRequestException;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.PrecisionModel;

@Embeddable
@NoArgsConstructor(access = PROTECTED)
Expand All @@ -23,21 +22,25 @@ public class Coordinate {
private static final double LONGITUDE_LOWER_BOUND = 124;
private static final double LONGITUDE_UPPER_BOUND = 132;

@Column(columnDefinition = "Decimal(18,15)")
private double latitude;
/*
* 4326은 데이터베이스에서 사용하는 여러 SRID 값 중, 일반적인 GPS기반의 위/경도 좌표를 저장할 때 쓰이는 값입니다.
* */
Comment on lines +25 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 그래서 SRID 값이 4326 이었던 거군용

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일종의 식별코드 같은 것이로군요

private static final GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);

@Column(columnDefinition = "Decimal(18,15)")
private double longitude;
@Column(columnDefinition = "geometry SRID 4326", nullable = false)
private Point coordinate;

private Coordinate(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
private Coordinate(Point point) {
this.coordinate = point;
}


public static Coordinate of(double latitude, double longitude) {
validateRange(latitude, longitude);

return new Coordinate(latitude, longitude);
Point point = geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(longitude, latitude));

return new Coordinate(point);
}

private static void validateRange(double latitude, double longitude) {
Expand All @@ -51,13 +54,12 @@ private static boolean isNotInRange(double latitude, double longitude) {
|| (longitude < LONGITUDE_LOWER_BOUND || LONGITUDE_UPPER_BOUND < longitude);
}

public double calculateDistanceInMeters(Coordinate otherCoordinate) {
double earthRadius = 6_371_000;
public double getLatitude() {
return coordinate.getY();
}

return acos(sin(toRadians(otherCoordinate.latitude)) * sin(toRadians(this.latitude)) + (
cos(toRadians(otherCoordinate.latitude)) * cos(toRadians(this.latitude))
* cos(toRadians(otherCoordinate.longitude - this.longitude))
)) * earthRadius;
Comment on lines -54 to -60
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

실제로 쓰이지 않고 있는 메서드를 삭제하고 테스트도 함께 지워주셨군요!
확인했습니다 👍

public double getLongitude() {
return coordinate.getX();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mapbefine.mapbefine.location.domain;

import java.util.List;
import org.locationtech.jts.geom.Point;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -11,15 +12,12 @@ public interface LocationRepository extends JpaRepository<Location, Long> {

@Query(
"SELECT l FROM Location l "
+ "WHERE ( 6371000 * acos( cos( radians(:#{#current_coordinate.latitude}) ) "
+ " * cos( radians( l.coordinate.latitude ) ) "
+ " * cos( radians( l.coordinate.longitude ) - radians(:#{#current_coordinate.longitude}) ) "
+ " + sin( radians(:#{#current_coordinate.latitude}) ) "
+ " * sin( radians( l.coordinate.latitude ) ) ) ) <= :distance"
+ "WHERE ST_Contains(ST_Buffer(:coordinate, :distance), l.coordinate.coordinate)"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ST_Contains(ST_Buffer(:coordinate, :distance), l.coordinate.coordinate)
이 부분 설명드릴게요.
ST_Buffer(A,B) 함수는 다음과 같습니다.
A는 특정 좌표를 나타내고, B는 거리를 나타냅니다. 이때 단위는 Meter이빈다.
A의 좌표로부터 B 거리 내에 존재하는 MBR을 반환해주는데요.
A의 좌표로부터 B 거리 내를 나타내는 것은 원이겠죠 ?
MBR은 이를 감싸는 사각형이라고 생각하시면 됩니다.

ST_Contains(A, B) 함수는 A라는 MBR(Minimum Bounding Rectangle)안에 B가 존재하는지 여부를 판단합니다.
즉, 위에서 이야기한 MBR 내에 존재하는 좌표 값들을 조회한다고 생각하시면 됩니다.

두 함수 모두 MySQL에서 지원하는 함수입니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

궁금한게 있어용 쥬니짱

말씀해주신 것처럼 MBR 은 A의 좌표로부터 B 거리 내를 나타내는 원을 감싸는 사각형이라고 하셨는데, 그렇다는 것은 MBR 은 원을 제외한 잉여공간이 있는 것인가요?? 사각형의 꼭짓점 부분들이요!

그렇다면 실제로 A의 좌표로부터 B 거리 이상 떨어진 좌표임에도 불구하고 조회해 올 수도 있는 건가요?

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞아요. 그래서 명확한 계산을 하기 위해서는 ST_DISTANCE 함수를 활용해야하는데, MySQL에서는 해당 함수를 사용할 때, 인덱싱을 처리해주지 않아요.

우리 서비스가 엄청 정확한 거리계산을 필요로하지 않는다는 점과, 실제 테스트해보지 않았지만 찾아본 결과 20배 가량의 성능 향상 결과가 나타난다는 점에서 위와 같은 결정을 내리게 되었어요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멋집니다~!

)
List<Location> findAllByCoordinateAndDistanceInMeters(
@Param("current_coordinate") Coordinate coordinate,
@Param("coordinate") Point coordinate,
@Param("distance") double distance
);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1. 개행!!


}
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ private Member findMember(Long memberId) {
private Location findDuplicateOrCreatePinLocation(PinCreateRequest request) {
Coordinate coordinate = Coordinate.of(request.latitude(), request.longitude());

return locationRepository.findAllByCoordinateAndDistanceInMeters(coordinate,
DUPLICATE_LOCATION_DISTANCE_METERS)
return locationRepository.findAllByCoordinateAndDistanceInMeters(
coordinate.getCoordinate(), DUPLICATE_LOCATION_DISTANCE_METERS
)
.stream()
.filter(location -> location.isSameAddress(request.address()))
.findFirst()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
public class DatabaseCleanup implements InitializingBean {

private static final String TRUNCATE_SQL_MESSAGE = "TRUNCATE TABLE %s";
private static final String SET_REFERENTIAL_INTEGRITY_SQL_MESSAGE = "SET REFERENTIAL_INTEGRITY %s";
private static final String SET_REFERENTIAL_INTEGRITY_SQL_MESSAGE = "SET FOREIGN_KEY_CHECKS = %s";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 MYSQL 로 변경되어서 그런건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넹 H2랑 문법이 다르니까여

private static final String DISABLE_REFERENTIAL_QUERY = String.format(SET_REFERENTIAL_INTEGRITY_SQL_MESSAGE, false);
private static final String ENABLE_REFERENTIAL_QUERY = String.format(SET_REFERENTIAL_INTEGRITY_SQL_MESSAGE, true);

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.mapbefine.mapbefine;

import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.MySQLContainer;


public abstract class TestDatabaseContainer {

private static final MySQLContainer mySQLContainer = new MySQLContainer("mysql:8.0.32");

static {
mySQLContainer.start();
}

@DynamicPropertySource
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동적으로 설정파일을 수정해줍니다 ~

컨테이너가 뜰때마다 포트번호가 다르다보니, url이 달라지잖아요 ?
그래서 사용합니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지렸네용

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

public static void overrideProps(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mySQLContainer::getJdbcUrl);
registry.add("spring.datasource.username", mySQLContainer::getUsername);
registry.add("spring.datasource.password", mySQLContainer::getPassword);
registry.add("spring.datasource.driver-class-name", mySQLContainer::getDriverClassName);
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

import com.mapbefine.mapbefine.TestDatabaseContainer;
import com.mapbefine.mapbefine.atlas.domain.Atlas;
import com.mapbefine.mapbefine.atlas.domain.AtlasRepository;
import com.mapbefine.mapbefine.bookmark.domain.Bookmark;
Expand Down Expand Up @@ -35,7 +36,7 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

@ServiceTest
class AdminCommandServiceTest {
class AdminCommandServiceTest extends TestDatabaseContainer {

@Autowired
private AdminCommandService adminCommandService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

import com.mapbefine.mapbefine.TestDatabaseContainer;
import com.mapbefine.mapbefine.admin.dto.AdminMemberDetailResponse;
import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse;
import com.mapbefine.mapbefine.common.annotation.ServiceTest;
Expand All @@ -26,7 +27,7 @@
import org.springframework.beans.factory.annotation.Autowired;

@ServiceTest
class AdminQueryServiceTest {
class AdminQueryServiceTest extends TestDatabaseContainer {

@Autowired
private AdminQueryService adminQueryService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.mapbefine.mapbefine.TestDatabaseContainer;
import com.mapbefine.mapbefine.atlas.domain.Atlas;
import com.mapbefine.mapbefine.atlas.domain.AtlasRepository;
import com.mapbefine.mapbefine.atlas.exception.AtlasException.AtlasForbiddenException;
Expand All @@ -27,7 +28,7 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

@ServiceTest
class AtlasCommandServiceTest {
class AtlasCommandServiceTest extends TestDatabaseContainer {

@Autowired
private TopicRepository topicRepository;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.mapbefine.mapbefine.TestDatabaseContainer;
import com.mapbefine.mapbefine.admin.application.AdminCommandService;
import com.mapbefine.mapbefine.auth.domain.AuthMember;
import com.mapbefine.mapbefine.common.annotation.ServiceTest;
Expand All @@ -24,7 +25,7 @@
import org.springframework.transaction.annotation.Transactional;

@ServiceTest
class AuthServiceTest {
class AuthServiceTest extends TestDatabaseContainer {

@Autowired
private AuthService authService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

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

import com.mapbefine.mapbefine.TestDatabaseContainer;
import com.mapbefine.mapbefine.auth.domain.token.RefreshToken;
import com.mapbefine.mapbefine.auth.domain.token.RefreshTokenRepository;
import com.mapbefine.mapbefine.auth.dto.LoginTokens;
import com.mapbefine.mapbefine.common.annotation.ServiceTest;
import com.mapbefine.mapbefine.member.MemberFixture;
import com.mapbefine.mapbefine.member.domain.Member;
import com.mapbefine.mapbefine.member.domain.MemberRepository;
Expand All @@ -22,9 +24,9 @@
import java.util.Date;
import java.util.Optional;

@DataJpaTest
@ServiceTest
@TestPropertySource(locations = "classpath:application.yml")
class TokenServiceTest {
class TokenServiceTest extends TestDatabaseContainer {

@Autowired
private RefreshTokenRepository refreshTokenRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.mapbefine.mapbefine.TestDatabaseContainer;
import com.mapbefine.mapbefine.auth.domain.AuthMember;
import com.mapbefine.mapbefine.bookmark.domain.Bookmark;
import com.mapbefine.mapbefine.bookmark.domain.BookmarkRepository;
Expand All @@ -21,7 +22,7 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

@ServiceTest
class BookmarkCommandServiceTest {
class BookmarkCommandServiceTest extends TestDatabaseContainer {

@Autowired
private BookmarkCommandService bookmarkCommandService;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mapbefine.mapbefine.common;

import com.mapbefine.mapbefine.DatabaseCleanup;
import com.mapbefine.mapbefine.TestDatabaseContainer;
import io.restassured.RestAssured;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -9,7 +10,7 @@
import org.springframework.boot.test.web.server.LocalServerPort;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IntegrationTest {
public class IntegrationTest extends TestDatabaseContainer {

@Autowired
protected TestAuthHeaderProvider testAuthHeaderProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mapbefine.mapbefine.TestDatabaseContainer;
import com.mapbefine.mapbefine.common.interceptor.AuthInterceptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -26,7 +27,7 @@
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@AutoConfigureRestDocs
public abstract class RestDocsIntegration {
public abstract class RestDocsIntegration extends TestDatabaseContainer {

@Autowired
protected ObjectMapper objectMapper;
Expand Down
Loading
Loading