728x90

https://www.youtube.com/watch?v=IiZZAu2Qtp0 

 

해당 Youtube 에서는 기초적인 Elastic Search 의 공사에 대해서 설명하고 있다.

 그러니까 기본적으로 하는 공사들에는 몇 몇가지가 있는데

 

일단 가장 첫 번째로 하는 것은

ELS 를 추상화한 클래스를 상속받아서 ELS에 대하여 Config 를 설정하는 별도의 Class를 만드는 것

(이 부분이 굉장히 중요하다 할 수 있을 것 같다.)

 

만약 데이터베이스를 사용하는 와중에 아래와 같은 가정이 있다고 가정해보면

 

1. AWS Aurora 를 사용하는 상태

2. 더욱 빠른 검색을 위해서 ElasticSearch 를 추가적으로 구현해야 하는 상태라면

 

JPA 에서는 MySQL 에 대한 package 를 별도로 지정해줘야 할 것이고, ELS 를 따르는 package 를 별도로 지정해줘야 할 것이다. 

 

그리고 나서 일반적으로 이루어지는 연계 사항들을 작성하게 되는데

Document Annotation과 함께 Class 내부의 Properties 를 정의하고, indicies 를 정의하여 인덱스화하는 것

 

그리고 나서 Person Class 가 정의되었다면, 그 Class 의 Properties 들을 활용하여 저장하는 Repository 에 대한 정의

그리고 Repository 에서 구현된 Method 를 호출하여 사용하는 Service

그리고 나서 REST 형식을 이용해 Service 에서 정의된 함수들을 호출하여 사용하는 Controller 를 정의한다.

(당연히 REST 니까 여기서는 GET, PUT, Delete Mapping 등이 전부 작용할 수 있다.)

 

728x90

환경설정 (Maven)

1) h2 DateBase 설치 및 시작
2) Maven 프로젝트 생성
3) pom.xml에 라이브러리 추가
4) presistence.xml 생성

1. h2 DB 설치 & 시작


  • 실행
    : h2.sh 파일을 실행하려면 chmod 755 h2.sh 수행해야 함

  • DB 생성 (최초 1회만 해주면 된다)
    : 경로가 ~/test 이니까 홈에 test.mv.db가 생성된다
    (안되면 url뒤에 키값을 확인해보자)

  • DB 들어가기
    : 최초 생성 이후에는 tcp로 파일에 접근

2. Maven 프로젝트 생성

  • Maven 선택 및 Java 버전 선택

3. pom.xml에 라이브러리 추가

4. persistence.xml 생성

  • persistence.xml란 ?
    : JPA를 사용하기 위해 참조되는 파일로 위치가 참조 정해져 있음
    (반드시 main/java/resource/META-INF/persistence.xml)
  • persistence-unit -> EntityManagerFactory 이름
  • hibernate.dialect 에 사용할 DBDialect를 변경할 수 있음
    ex) MySQLDialect 
  • JPA에 대한 옵션을 추가할 수 있음

JPA 구동방식 & 사용


  • EntityManagerFactory를 가져오기
  • EntityManager 꺼내기
  • Transaction을 시작
  • 쿼리 내용을 수행
  • Transaction 종료
  • EntityManager  EntityManagerFactory닫기

JPQL ?

  • Table이 아닌 객체를 중심으로 하는 객체 지향 쿼리
  • 검색을 할 때도 테이블 이 아닌 Entity 객체를 대상으로 검색
  • SQL 문법과 유사
  • SQL을 추상화해서 특정 DB SQL에 의존하지 않음
728x90

https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide#search-book

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

이걸 써보려고 한다.

 

일단 시작전에 postman으로 api를 실행해보겠다.

Auth에서 저렇게 설정하고 send 해보면

 

잘 나온다. 이제 이걸 적용하는게 문제다.

 

이런저런 구글링을 하니까 RestTemplate라는걸 사용하면 스프링에서 Rest Api를 호출할 수 있나보다.

https://spring.io/guides/gs/consuming-rest/

 

Consuming a RESTful Web Service

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

뭔소리여

 

https://hyeonyeee.tistory.com/34

 

RestTemplate 를 이용하여 API Get 하기

1. RestTemplate이란? Spring 3.0부터 지원하며 스프링에서 http 통신에 유용하게 쓸수 있도록 제공해주는 템플릿이다. 즉, API 통신해서 가져 올수 있도록 도와준다. 2. 예시코드 @GetMapping("/test") public Re..

hyeonyeee.tistory.com

https://recordsoflife.tistory.com/360

 

RestTemplate 사용방법(예제)

Java 에코 시스템에서 몇 년간의 경험이 있고 그 경험을 커뮤니티와 공유하고 (물론 작업에 대한 대가를받는 데) 관심이 있다면 "Write for Us"페이지를 참조하십시오 . 건배, Eugen 1. 개요 이 튜토리얼

recordsoflife.tistory.com

이런 저런 블로그를 찾아봤고...

 

package jpa.myunjuk.common;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Map;

@RestController
public class KakaoApi {

    private final String url = "https://dapi.kakao.com/v3/search/book";
    private final String key = "rest api key";

    @GetMapping("/kakao")
    public Map callApi(@RequestParam String query){
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set("Authorization", "KakaoAK "+key);
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url).queryParam("query", query);
        HttpEntity<String> entity = new HttpEntity<>(httpHeaders);

        return restTemplate.exchange(builder.toUriString(), HttpMethod.GET, entity, Map.class).getBody();
    }
}

여기까지 했다. 설명은 이따가 하기로 하고...

 

잘 나오긴 하는데 한글이 깨진다.

 

 

악 됐다!

 

https://cultist-tp.tistory.com/entry/Spring-RestTemplate-%EA%B0%9D%EC%B2%B4-%EC%82%AC%EC%9A%A9%EC%8B%9C-url%EC%97%90-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%A0%84%EB%8B%AC-%EB%B0%A9%EB%B2%95

 

Spring RestTemplate 객체 사용시, url에 파라미터 전달 방법

참조 : http://stackoverflow.com/questions/15774475/how-to-send-a-getforobject-request-with-parameters-spring-mvc RestTemplate 객체를 사용하여 url 주소를 호출과 동시에 파라미터를 전달 해줄 경우 Uri..

cultist-tp.tistory.com

여기 보고 해결했다.

 

package jpa.myunjuk.common;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@RestController
public class KakaoApi {

    @Value("${kakao.key}")
    private String key;
    private String url = "https://dapi.kakao.com/v3/search/book";

    @GetMapping("/kakao")
    public Map callApi(@RequestParam String query) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set("Authorization", "KakaoAK " + key); //Authorization 설정
        HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders); //엔티티로 만들기
        URI targetUrl = UriComponentsBuilder
                .fromUriString(url) //기본 url
                .queryParam("query", query) //인자
                .build()
                .encode(StandardCharsets.UTF_8) //인코딩
                .toUri();

        //GetForObject는 헤더를 정의할 수 없음
        ResponseEntity<Map> result = restTemplate.exchange(targetUrl, HttpMethod.GET, httpEntity, Map.class);
        return result.getBody(); //내용 반환
    }
}

에러 핸들링을 안해서 코드가 짧다. 해야지...해야 하는데...

아무튼 대충 설명하면

 

    @Value("${kakao.key}")
    private String key;
    private String url = "https://dapi.kakao.com/v3/search/book";

일단 검색해본 결과 괜찮다곤 했는데, 키를 노출하는게 영 찝찝해서 application.yml에 api key를 보관했다.

 

application.yml

kakao:
  key: key 내용

이런식으로 하고 저렇게 value 어노테이션으로 호출하면 된다.

 

    @GetMapping("/kakao")
    public Map callApi(@RequestParam String query) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set("Authorization", "KakaoAK " + key); //Authorization 설정
        HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders); //엔티티로 만들기
        URI targetUrl = UriComponentsBuilder
                .fromUriString(url) //기본 url
                .queryParam("query", query) //인자
                .build()
                .encode(StandardCharsets.UTF_8) //인코딩
                .toUri();

        //GetForObject는 헤더를 정의할 수 없음
        ResponseEntity<Map> result = restTemplate.exchange(targetUrl, HttpMethod.GET, httpEntity, Map.class);
        return result.getBody(); //내용 반환
    }

그리고 여긴 실제로 구현할 땐 Service로 바꾸겠지만 일단 테스트라 API로 만들어서 바로 호출했다.

 

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();

RestTemplate는 기본이고, HttpHeaders는 Authorization 때문에 추가했다.

 

 

        httpHeaders.set("Authorization", "KakaoAK " + key); //Authorization 설정
        HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders); //엔티티로 만들기

저기 써있는데로 헤더에 인증 정보를 입력하고, 엔티티로 만들어 준다. post 호출일 때는 엔티티에 body도 넘긴다고 한 것 같다.

 

        URI targetUrl = UriComponentsBuilder
                .fromUriString(url) //기본 url
                .queryParam("query", query) //인자
                .build()
                .encode(StandardCharsets.UTF_8) //인코딩
                .toUri();

그리고 요청을 보낼 uri를 만든다. 개발가이드 보면 인자는 query로 넘겨야 한다. 그래서 그렇게 넘긴 것이다.

UTF-8로 인코딩하면 한글이 깨지지 않는다.

 

        //GetForObject는 헤더를 정의할 수 없음
        ResponseEntity<Map> result = restTemplate.exchange(targetUrl, HttpMethod.GET, httpEntity, Map.class);
        return result.getBody(); //내용 반환

마지막으로 exchange 메소드를 이용해서 호출하면 된다. 

getHeader()를 하면 헤더 정보가 나오고 getBody()를 하면 내용이 반환되는가보다.

 

인텔리제이피셜 넘겨야하는 인자는 위와 같다.

 

아무튼 잘 나온다!

 

하지만 여기서 끝난게 아니고 이 결과를 담을 Dto도 만들어야 하고 뭐 암튼 이것저것 할 것들이 많다.

728x90

목표

Spring Boot에서 JPA 사용하기

 

환경

  • Framework : Spring Boot 2.6.7
  • Build : Gradle 6.9.2
  • JDK : JDK11

 

할 것 요약

1. build.gradle에 JPA dependency 추가하기

2. application.yml에 JPA 설정 추가하기

3. JPA Entity 생성

4. JPA Repository 생성

5. JPA CRUD API 만들어보기

 

해보기

1. build.gradle에 JPA dependency 추가하기

dependencies {
    // JPA
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

 

2. application.yml에 JPA 설정 추가하기

spring:
  ## JPA Setting Info
  jpa:
    hibernate:
      ddl-auto: create  # option type: create, create-drop, update, validate, none
    properties:
      hibernate:
        diarect: org.hibernate.dialect.H2Dialect  # 쿼리의 기준이 되는 데이터베이스 엔진을 설정합니다.
        show_sql: true  # sql 쿼리를 보여줍니다.
        format_sql: true  # sql query formatting
        use_sql_comments: true  # sql 쿼리의 추가정보를 보여줍니다.

logging:
  level:
    org:
      hibernate:
        type:
          descriptor:
            sql: trace  # query의 ?에 어떤 값이 들어가는지 추적할 수 있는 설정입니다. TRACE Log에 값을 보여줍니다.

spring.jpa.hibernate.ddl-auto 옵션

create 애플리케이션 실행 시 테이블을 모두 제거하고 다시 생성합니다. (drop & create)
create-drop 애플리케이션 실행 시 테이블을 모두 제거하고 다시 생성합니다. 그리고 애플리케이션 종료 시점에 테이블을 모두 제거합니다. (drop & create & drop)
update 애플리케이션 실행 시 변경점만 반영됩니다.
validate 현재 테이블 정보가 entity에 정의된 내용과 동일한지 체크합니다. 다를 경우 경고를 출력하며 애플리케이션을 실행시키지 않습니다.
none 자동생성을 사용하지 않습니다.

 

spring.jpa.properties.hibernate.show_sql: true

 

spring.jpa.properties.hibernate.format_sql: true

 

spring.jpa.properties.hibernate.use_sql_comments: true

 

logging.level.org.hibernate.type.descriptor.sql: trace 설정

 

3. JPA Entity 생성

// Member.java

package com.herojoon.jpaproject.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.sun.istack.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import javax.persistence.*;
import java.util.Date;

@SuperBuilder
@NoArgsConstructor
@Setter
@Getter
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})  // JPA에서 lazy관련 에러 날 경우 사용
@Entity  // 객체와 테이블 매핑
@Table(name = "MEMBER")  // 테이블 지정
public class Member {
    @Id  // Primary Key 지정
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // AUTO_INCREMENT 설정 (id값이 null일 경우 자동 생성)
    @Column(name = "ID")  // 컬럼 지정
    private Long id;

    @NotNull
    @Column(name = "NAME")
    private String name;

    @NotNull
    @Column(name = "email")
    private String email;

    @Column(name = "NICKNAME")
    private String nickname;

    @Column(name = "AGE")
    private Integer age;

    @Column(name = "BIRTHDAY")
    private Date birthday;
}

Entity에 정의한 내용으로 테이블이 생성됩니다.

 

4. JPA Repository 생성

// MemberRepository.java

package com.herojoon.jpaproject.repository;

import com.herojoon.jpaproject.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {  // JpaRepository를 상속하여 사용. <객체, ID>
}

 

5. JPA CRUD API 만들어보기

// MemberController.java

package com.herojoon.jpaproject.controller;

import com.herojoon.jpaproject.entity.Member;
import com.herojoon.jpaproject.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

@RequiredArgsConstructor
@RequestMapping("api/member")
@RestController
public class MemberController {

    private final MemberService memberService;

    /**
     * Member 생성
     *
     * @return
     * @throws ParseException
     */
    @PostMapping("create")
    public ResponseEntity<Member> createMember() throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = format.parse("2011-12-03");
        Member member = Member.builder()
                .name("herojoon")
                .email("herojoon432@gmail.com")
                .nickname("heroble")
                .age(10)
                .birthday(date)
                .build();
        Member savedMember = memberService.createMember(member);
        return new ResponseEntity<>(savedMember, HttpStatus.OK);
    }

    /**
     * Member 수정
     *
     * @return
     * @throws ParseException
     */
    @PutMapping("update")
    public ResponseEntity<Member> updateMember() throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = format.parse("2011-12-03");
        Member member = Member.builder()
                .id(1l)
                .name("herojoon2")
                .email("herojoon432@gmail.com")
                .nickname("heroble2")
                .age(10)
                .birthday(date)
                .build();
        Member updatedMember = memberService.updateMember(member);
        if (!ObjectUtils.isEmpty(updatedMember)) {
            return new ResponseEntity<>(updatedMember, HttpStatus.OK);
        } else {
            return new ResponseEntity<>(member, HttpStatus.NOT_FOUND);
        }
    }

    /**
     * Member List 조회
     *
     * @return
     */
    @GetMapping("list")
    public ResponseEntity<List<Member>> getMembers() {
        List<Member> members = memberService.getMembers();
        return new ResponseEntity<>(members, HttpStatus.OK);
    }

    /**
     * Id에 해당하는 Member 조회
     *
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public ResponseEntity<Member> getMember(
            @PathVariable("id") Long id) {
        Member member = memberService.getMember(id);
        return new ResponseEntity<>(member, HttpStatus.OK);
    }

    /**
     * Id에 해당하는 Member 삭제
     *
     * @param id
     * @return
     */
    @DeleteMapping("{id}")
    public ResponseEntity<Long> deleteMember(
            @PathVariable("id") Long id) {
        memberService.deleteMember(id);
        return new ResponseEntity<>(id, HttpStatus.OK);
    }
}
// MemberService.java

package com.herojoon.jpaproject.service;

import com.herojoon.jpaproject.entity.Member;
import com.herojoon.jpaproject.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    /**
     * Member 생성
     *
     * @param member
     * @return
     */
    public Member createMember(Member member) {
        Member savedMember = memberRepository.save(member);  // JpaRepository에서 제공하는 save() 함수
        return savedMember;
    }

    /**
     * Member 수정
     * JPA Repository의 save Method를 사용하여 객체를 업데이트 할 수 있습니다.
     * Entity Member에 @Id로 설정한 키 값이 존재할 경우 해당하는 데이터를 업데이트 해줍니다.
     * 만약 수정하려는 Entity Member 객체에 @Id 값이 존재하지 않으면 Insert 되기 때문에
     * 아래와 같이 업데이트 하고자 하는 Member가 존재하는지 체크하는 로직을 추가하였습니다.
     *
     * @param member
     * @return
     */
    public Member updateMember(Member member) {
        Member updatedMember = null;
        try {
            Member existMember = getMember(member.getId());
            if (!ObjectUtils.isEmpty(existMember)) {
                updatedMember = memberRepository.save(member);  // JpaRepository에서 제공하는 save() 함수
            }
        } catch (Exception e) {
            log.info("[Fail] e: " + e.toString());
        } finally {
            return updatedMember;
        }
    }

    /**
     * Member List 조회
     * 
     * @return
     */
    public List<Member> getMembers() {
        return memberRepository.findAll();  // JpaRepository에서 제공하는 findAll() 함수
    }

    /**
     * Id에 해당하는 Member 조회
     * 
     * @param id
     * @return
     */
    public Member getMember(Long id) {
        return memberRepository.getById(id);  // JpaRepository에서 제공하는 getById() 함수
    }

    /**
     * Id에 해당하는 Member 삭제
     * 
     * @param id
     */
    public void deleteMember(Long id) {
        memberRepository.deleteById(id);  // JpaRepository에서 제공하는 deleteById() 함수
    }
}
 

+ Recent posts