음식점 정보를 스크래핑하여 View 단에서 볼 수 있도록 작업을 합니다
기존 프로젝트에서는 H2 DB와 JPA 를 사용하여 따로 ddl 작업을 거치지 않았는데 실제 운영되는 서비스라면 이러한 작업은 매우 위험하며 수정할 필요가 있기에 AWS 에 배포를 하기전 mySql 로 DB를 옮기는 작업을 선행합니다
또한 앞에서 구한 사용자의 좌표값을 통하여 Service에서 비즈니스 로직을 수행할 수 있도록 수정하는 작업을 거칩니다
기존에 사용하던 h2 DB는 사용하지 않으니 디펜던시에서 제거해준뒤 build를 업데이트 해줍니다
MySql 이 설치되어 있으며 User 가 있다는 가정하에 진행합니다 (이에 관련된 포스팅은 참고 자료가 많으니 생략)
CREATE DATABASE Lunch_Recommend default CHARACTER SET UTF8;
CREATE USER admin@localhost IDENTIFIED BY '1234';
GRANT ALL PRIVILEGES ON Lunch_Recommend.* TO admin@localhost;
spring.datasource.url=jdbc:mysql://localhost:3306/Lunch_Recommend?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
spring.datasource.username=admin
spring.datasource.password=1234
spring.jpa.hibernate.ddl-auto=create
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#hibernate ??
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
# jpa ??
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
# ?? ???
spring.session.store-type=jdbc
allowPublicKeyRetrieval
MySql 8.0 버전부터는 allowPublicKeyRetrieval 설정을 해주어야 Public key retrieval is not allowed 에러를 피할 수 있습니다.
< 속성 >
- useSSL: DB에 SSL로 연결
- allowPublicKeyRetrieval: 서버에서 RSA 공개키를 검색하거나 가져와야하는지
h2 DB에서는 ddl-auto 가 기본적으로 적용되는 것으로 보였지만 Mysql로 바꾸면서는 따로 설정해두어야 합니다
실제 서비스시에는 ddl-auto 설정을 풀고 새롭게 DB에 저장하도록 수정을 거칩니다
아직 배포전에는 RDS 를 사용하지 않을 것이기 때문에 따로 스프링 세션에 대한 테이블을 만들어주어야 합니다
세션 관리 테이블 생성
spring session 디펜던시를 적용했다면 schema-h2.sql 를 검색하면 다음과 같은 파일이 저장되어 있을것입니다.
이는 H2 데이터베이스 문법이 적용된 스키마이므로 MySql version으로 바꾸어 줍니다
문제없이 잘 작동하는 것을 볼 수 있습니다
@SpringBootApplication
@EnableJpaAuditing
public class LunchSolverApplication {
public static void main(String[] args) {
SpringApplication.run(LunchSolverApplication.class, args);
}
}
생각해보니 EnableJpaAuditing 을 적용하지 않아 적용되지 않는 문제였습니다 (스프링 부트 1.x를 쓴다면 별도로 Hibernate 5.2.10 버전 이상을 사용하도록 설정이 필요)
@Test
@DisplayName("카테고리별 100개 스크래핑하여 DB에 담기")
public void getRestaurantData_v2 () throws Exception {
for (RestaurantType type : RestaurantType.values()) {
// 카테고리내의 모든 음식들을 크롤링
String url = "/graphql";
String _url = HOST_v2+url;
GetRestaurantRequest request = GetRestaurantRequest.builder()
.x(x)
.y(y)
.bounds("126.9738873;37.5502692;126.9980272;37.5696434")
.query("음식점")
.type(type)
.build();
String jsonOperation = naverUtility.getRestaurants(request);
HttpHeaders httpHeaders = utility.getDefaultHeader();
HttpEntity requestMessage = new HttpEntity(jsonOperation,httpHeaders);
ResponseEntity response = restTemplate.exchange(
_url,
HttpMethod.POST,
requestMessage,
String.class);
List<Restaurant> entities = new ArrayList<>();
JSONArray datas = new JSONArray(response.getBody().toString());
datas.getJSONObject(0);
JSONArray items = datas.getJSONObject(0).getJSONObject("data").getJSONObject("restaurants").getJSONArray("items");
int total = Integer.parseInt(datas.getJSONObject(0).getJSONObject("data").getJSONObject("restaurants").getString("total"));
int maxCnt = total<100? total:100;
for (int i = 0; i < maxCnt; i++) {
GetRestaurantResponse mapped_data = gson.fromJson(items.getString(i),GetRestaurantResponse.class);
//1. first map with entity : 엔티티와 매핑하기전 validation을 거친다
Restaurant restaurant = Restaurant.builder()
.id(Long.parseLong(mapped_data.getId()))
.address(mapped_data.getAddress())
.category(mapped_data.getCategory()==null?"없음": mapped_data.getCategory())
.imageUrl(mapped_data.getImageUrl()==null?"":URLDecoder.decode(mapped_data.getImageUrl(),"UTF-8"))
.name(mapped_data.getName())
.distance(utility.stringToLongDistance(mapped_data.getDistance()))
.businessHours(mapped_data.getBusinessHours())
.visitorReviewScore(mapped_data.getVisitorReviewScore()==null? 0.0 : Double.parseDouble(mapped_data.getVisitorReviewScore()))
.saveCount(utility.stringToLongSaveCnt(mapped_data.getSaveCount()))
.bookingReviewScore(mapped_data.getBookingReviewScore())
.restaurantType(type)
.build();
entities.add(restaurant);
}
restaurantsRepository.saveAll(entities);
}
List<Long> ids = restaurantsRepository.findAllreturnId();
for (Long id : ids) {
System.out.println(id);
}
}
기존 서비스에서 사용되던 비즈니스 로직입니다.
수정 후
@Transactional
@Override
public void getRestaurantData(GetRestaurantRequest getRestaurantRequest) throws UnsupportedEncodingException {
for (RestaurantType type : RestaurantType.values()) {
// 카테고리내의 모든 음식들을 크롤링
GetRestaurantRequest request = GetRestaurantRequest.builder()
.x(getRestaurantRequest.getX())
.y(getRestaurantRequest.getY())
.bounds(getRestaurantRequest.getBounds())
.query("음식점")
.type(type)
.build();
log.info("Service 에서 모델 : "+request);
String _url = HOST_v2;
String jsonOperation = naverUtility.getRestaurants(request);
HttpHeaders httpHeaders = utility.getDefaultHeader();
HttpEntity requestMessage = new HttpEntity(jsonOperation, httpHeaders);
ResponseEntity response = restTemplate.exchange(
_url,
HttpMethod.POST,
requestMessage,
String.class);
List<Restaurant> entities = new ArrayList<>();
JSONArray datas = new JSONArray(response.getBody().toString());
datas.getJSONObject(0);
JSONArray items = datas.getJSONObject(0).getJSONObject("data").getJSONObject("restaurants").getJSONArray("items");
int total = Integer.parseInt(datas.getJSONObject(0).getJSONObject("data").getJSONObject("restaurants").get("total").toString());
int maxCnt = total < 100 ? total : 100;
for (int i = 0; i < maxCnt; i++) {
GetRestaurantResponse mapped_data = gson.fromJson(items.get(i).toString(), GetRestaurantResponse.class);
//1. first map with entity : 엔티티와 매핑하기전 validation을 거친다
Restaurant restaurant = Restaurant.builder()
.id(Long.parseLong(mapped_data.getId()))
.address(mapped_data.getAddress())
.category(mapped_data.getCategory() == null ? "없음" : mapped_data.getCategory())
.imageUrl(mapped_data.getImageUrl() == null ? "" : URLDecoder.decode(mapped_data.getImageUrl(), "UTF-8"))
.name(mapped_data.getName())
.distance(utility.stringToLongDistance(mapped_data.getDistance()))
.businessHours(mapped_data.getBusinessHours())
.visitorReviewScore(mapped_data.getVisitorReviewScore() == null ? 0.0 : Double.parseDouble(mapped_data.getVisitorReviewScore()))
.saveCount(utility.stringToLongSaveCnt(mapped_data.getSaveCount()))
.bookingReviewScore(mapped_data.getBookingReviewScore())
.restaurantType(type)
.build();
entities.add(restaurant);
}
log.info("saving in service succeed");
restaurantsRepository.saveAll(entities);
}
}
@PutMapping("/getRestaurantData")
public void getRestaurantData(GetRestaurantRequest request){
try {
String bounds = String.format("%s;%s;%f;%f",
request.getX(),
request.getY(),
Double.parseDouble(request.getX())+0.0241399,
Double.parseDouble(request.getY())+0.0193742);
log.info("bounds : "+bounds);
request.setBounds(bounds);
restaurantService.getRestaurantData(request);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
function getFullAddress(longtitude, latitude) {
var AddressRequest={
x : longtitude,
y : latitude
};
$.ajax({
url: "/user/api/getFullAddress",
data: AddressRequest,
type:"POST",
}).done(function (fragment) {
console.log('done');
$("#fullAddress").replaceWith(fragment);
getRestaurantData(longtitude, latitude);
});
}
function getRestaurantData(longtitude, latitude){
var GetRestaurantRequest={
x : longtitude,
y : latitude
};
$.ajax({
url: "/user/api/getRestaurantData",
data: GetRestaurantRequest,
type:"PUT",
}).done(function (fragment) {
console.log('input succeed');
});
}
VIEW
SERVICE
DATABASE