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() 함수
    }
}
 
728x90

기존 데스크탑에서 자바어플리케이션 개발 및 배포를 해오다 얼마전부터 데스크탑의 맛탱이가는 증세를 보고 개발환경을 노트북으로 바꾸는 과정이었다.

jdk최신버전 설치후 STS4설치하고 war import해서 로컬에서 빌드하니 정상적으로 나왔다. 서버쪽 수정 후 서버에 배포하니 웹서비스들이 미쳐돌아가고 있었다. 하나는 500에러를 내뿜고 하나는 고양이만 나오고 있었다. 분명 심각한 문제가 발생한 것이다.

톰캣 로그를 보았다.

Caused by: java.lang.UnsupportedClassVersionError: kr/pe/innu/stock/dao/StockDaoImpl has been compiled by a more recent version of the Java Runtime (class file version 61.0), 
this version of the Java Runtime only recognizes class file versions up to 52.0 
(클래스 [kr.pe.innu.stock.dao.StockDaoImpl]을(를) 로드할 수 없습니다)
...
org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [kr.pe.innu.stock.service.StockServiceImpl] for bean with name 'stockService' defined in ServletContext resource 
[/WEB-INF/spring/appServlet/servlet-context.xml]: problem with class file or dependent class; nested exception is java.lang.UnsupportedClassVersionError: kr/pe/innu/stock/service/StockServiceImpl has been compiled by a more recent version of the Java Runtime 
(class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0 
(클래스 [kr.pe.innu.stock.service.StockServiceImpl]을(를) 로드할 수 없습니다)

대충보니 클래스파일은 version 61로 되어 있는데 이 시스템은 version 52까지 인식하는거 같다.

노트북에 jdk 최신버전(J2SE 17)을 설치했었는데 아무래도 이게 문제인거 같다. 우선 개발환경과 배포환경의 jdk버전을 맞추는게 필요했다. 최신버전보다는 이전버전으로 개발환경의 jdk(J2SE8)를 다시 설치했다.

그리고 STS의 컴파일러 적용버전을 바꿔주었다.

STS를 다시 켜서 빌드를 하고 클래스버전을 확인하니 배포서버의 버전과 같았고 정상 배포됨을 확인했다.

자바 클래스 버전은 jdk1.1이 메이저버전 45 이고 이후 1씩 증가한다. 나의 배포서버는 Centos7이며 J2SE8(메이저버전 52)이고 현재 최신버전은 J2SE17(메이저버전 61)이다.

+ Recent posts