20221004 [Spring Boot, API, JPA]

Yeoonnii·2022년 10월 6일
0

TIL

목록 보기
39/52
post-thumbnail

API 사용시 https 인증서 처리

가져오는 API의 url 형태에 따라 인증서 필요여부가 달라진다

  • http = 인증서 필요없음
  • https = 인증서 필요
    ➡️ 주소가 https로 시작하는경우 인증서를 무시하도록 설정해준다

pom.xml

인증서 처리를 위한 라이브러리 설치

		<!-- httpcomponents -->
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.13</version>
		</dependency>

		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpcore</artifactId>
			<version>4.4.14</version>
		</dependency>

		<!-- json -->
		<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>20211205</version>
		</dependency>

ApiRestController.java

https는 인증서 무시하는 부분을 넣어줘야 정상적으로 API 이용이 가능하다
clientHttpRequestFactory() ➡️ 인증서 무시

package com.example.controller;

import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

@Controller
@RequestMapping(value="/item")
public class ItemController {
   

    //127.0.0.1:8080/BOOT1/item/test001
    @GetMapping(value="/test001")
    public @ResponseBody Object test001(){
        String url = "https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst?serviceKey=nVhOnXku3h%2BeR4sJ%2FaQ7BHaiFaHZJjc9JHlTZEa4hlp6CmZP9rJkSoySma0tnO6bZPDTwcIJ%2FK4o4TZEVPy0Dw%3D%3D&pageNo=1&numOfRows=1000&dataType=JSON&base_date=20221004&base_time=0500&nx=55&ny=127";
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());        
        String response = restTemplate.getForObject(url, String.class);

        
        if(response != null){
            System.out.println(response.toString());

            return response.toString();
        }
        return null;
    }

	// 이 메서드를 사용하면 인증서를 무시할 수 있다
    //throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory()  {
		try{
            TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

            SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
            
            SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
            
            HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
            requestFactory.setHttpClient(httpClient);
            requestFactory.setConnectTimeout(5 * 1000);    
            requestFactory.setReadTimeout(5 * 1000);
            
            return requestFactory;
        }
        catch(Exception e){
            e.printStackTrace();
            return null;
        }
    }
	
}

JPA

JPA실습을 위한 새 프로젝트 생성

JPA 라이브러리/환경설정

JPA 라이브러리 설치
💡 mybatis 제외하고 설치

pom.xml

<!-- jpa -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

환경설정에서 JPA ddl-auto 설정

ddl-auto란 JPA에서 기본적으로 Entity에 테이블을 매핑하는 경우 쿼리를 사용하지 않고 값을 가져올 때 설정하는 옵션을 말한다

아래와 같은 옵션이 있다

  • create ➡️ 서버 시작시 DDL 삭제 후 다시 자동 생성
  • create-drop ➡️ create와 같으나 서버 종료시점에 테이블 DROP
  • update ➡️ 변경항목만 적용
  • validate ➡️ 정상 매핑만 확인
  • none ➡️ 아무작업 없음

application.properties

환경설정

#  127.0.0.1:8080/ROOT/
server.port=8080
server.servlet.context-path=/ROOT

# 소스코드 변경시 자동으로 서버구동하기
spring.devtools.livereload.enabled=true

# view에 해당하는 html의 위치 설정
# cache=false 개발시, 서비스 배포시에는 true
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

# h2(임베디드DB, 개발용 DB)
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:tcp://1.234.5.158:21521/ds207;Mode=Oracle
spring.datasource.username=sa
spring.datasource.password=

#  서버로 배포시 오류방지용
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true

spring.datasource.hikari.connection-test-query=SELECT 1 FROM DUAL
spring.datasource.hikari.connection-timeout=600000
spring.datasource.hikari.maximum-pool-size=500
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.minimum-idle=20
spring.datasource.hikari.validation-timeout=3000
spring.datasource.hikari.idle-timeout=60000

#create => 서버 시작시 DDL 자동생성
#create-drop => 자동생성 + 자동삭제 
#update => 변경항목만 적용
#invalidate => 정상 매핑만 확인
#none => 아무작업 없음

#테이블 및 시퀀스를 DDL로 직접 생성
spring.jpa.hibernate.ddl-auto=none

JPA entity 생성

  • entity 생성 후 서버 실행시 DB에 테이블이 생성된다
  • entity 생성시에는 서버를 실행하지 말고 꺼뒀다가 완성 후 실행시 생성된다
  • JPA는 엔티티와 데이터 매칭이 완벽하지 않으면 서버가 실행되지 않는다

entity/Board.java

  • @Entity : entity 생성시 @Entity 어노테이션을 붙여 명시해준다

  • @Table(name = "BOARDTBL1") = 테이블명, name으로 테이블명 지정시 해당이름이 테이블명이다
    테이블명 지정 생략시 클래스명이 테이블명이 된다

  • @SequenceGenerator(name = "SEQ1", sequenceName = "SEQ_BOARDTBL1_NO", initialValue = 1, allocationSize = 1) = 시퀀스 생성하여 사용가능

    • name = "SEQ1" 엔티티 내부에서 사용하기 위한 명칭
    • sequenceName = "SEQ_BOARDTBL1_NO" : 실제 시퀀스 이름
  • @Id : 테이블의 기본키가 될 컬럼에 기본키 지정해준다

  • @Column(name = "TITLE", length = 300) : 컬럼명을 name으로 지정 가능하고, 컬럼의 길이 length 도 지정 가능하다. 컬럼명 지정을 생략시 변수명이 컬럼명으로 지정된다

@Getter
@Setter
@ToString
@NoArgsConstructor

// 엔티티라고 어노테이션을 붙여 명시
@Entity
// 테이블명 //생략하면 클래스명이 테이블명 EX)BOARD //테이블명 지정시 해당이름이 테이블명
@Table(name = "BOARDTBL1")
// 시퀀스 생성,사용가능
// name = "SEQ1" 엔티티에서 사용하기 위한 명칭
// sequenceName = "SEQ_BOARDTBL1_NO" 실제 시퀀스 이름
@SequenceGenerator(name = "SEQ1", sequenceName = "SEQ_BOARDTBL1_NO", initialValue = 1, allocationSize = 1)
public class Board {
    @Id // 기본키 
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ1")
    private Long no = 0L;

    // 컬럼명을 생략하면 변수명이 컬럼명 ex)title , 길이도 지정가능 length
    @Column(name = "TITLE", length = 300)
    private String title = null;
    private String content = null;

    @Column(name = "HIT")
    private Long hit = 1L;
    
    private String writer = null;

    @CreationTimestamp
    // updatable => 수정시에도 날짜 갱신/변경여부
    @Column(name = "REGDATE", updatable = false)
    private Date regdate = null;

}

게시판 글쓰기

BoardRepository.java

repository 생성

기존에 생성되어있는 저장소 JpaRepository를 상속받아 사용한다
➡️ JpaRepository<엔티티, 엔티티의 기본키타입>

package com.example.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.entity.Board;

@Repository
// 기존에 생성되어있는 저장소 상속<엔티티, 엔티티의 기본키타입>
public interface BoardRepository extends JpaRepository<Board, Long> {
    
}

BoardController.java

// 게시글 작성페이지로 이동
    @GetMapping(value = "/insert.do")
    public String insertGET(){
        // view로 전송할 값이 없음
        return "board_insert";
    }

    // 게시글 작성하기
    @PostMapping(value="/insert.do")
    public String insertPOST(@ModelAttribute Board board) {
        System.out.println("===============");
        System.out.println(board.toString());
        Board ret = bRepository.save(board);
        System.out.println("=========ret========");
        if(ret == null){

            return "redirect:/board/insert.do";
        }
        return "redirect:/board/select.do";
    }

board_insert.html 생성

spring.jpa.hibernate.ddl-auto=create로 변경하여 테이블 만들고 none으로 유지

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>게시판 글쓰기</title>
</head>

<body>
    <a th:href="@{/home.do}">홈으로</a>
    <hr />
    게시판글쓰기
    <hr />
    
    <form th:action="@{/board/insert.do}" method="post">
            <input type="text"  name="title" placeholder="제목"  /><br />
            <input type="text"  name="content" placeholder="내용" /><br />
            <input type="text"  name="writer" placeholder="작성자"  /><br />
            <br />
        <hr />
        <input type="submit" value="일괄추가" />
    </form>
</body>
</html>

게시글 목록조회

BoardController.java

spring.jpa.hibernate.ddl-auto=none 으로 변경

// 게시글 조회하기
    @GetMapping(value = "/select.do")
    public ModelAndView selectGET(){
        List<Board> list = bRepository.findAll();
        System.out.println(list);

        // model.addAttribute("list", list)
        // return "board_select";
        // ModelAndView는 위 두줄을 동시에 진행
        return new ModelAndView("board_select", "list", list);
    }

board_select.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>게시판 글목록</title>
</head>

<body>
    <a th:href="@{/home.do}">홈으로</a>
    <a th:href="@{/board/insert.do}">게시글 등록</a>
    <table border="1">
        <tr>
            <th>글번호</th>
            <th>글제목</th>
            <th>작성자</th>
            <th>글내용</th>
            <th>조회수</th>
            <th>등록일</th>
            <th>버튼</th>
        </tr>
        <tr th:each="obj, idx : ${list}">
            <td th:text="${obj.no}"></td>
            <td th:text="${obj.title}"></td>
            <td th:text="${obj.writer}"></td>
            <td th:text="${obj.content}"></td>
            <td th:text="${obj.hit}"></td>
            <td th:text="${obj.regdate}"></td>
            <td>
                <form th:action="@{/board/delete.do}" method="post">
                    <input type="hidden" name="no" th:value="${obj.no}" />
                    <input type="submit" value="삭제" />
                </form>
                <form th:action="@{/board/update.do}" method="get">
                    <input type="hidden" name="no" th:value="${obj.no}" />
                    <input type="submit" value="수정" />
                </form>
            </td>
        </tr>
    </table>
</body>
</html>

게시글 수정

BoardController.java

bRepository.save() 사용하여 수정
기본키정보가 들어있는 엔티티를 넣으면 수정된다
💡 기본키 정보가 없으면 자동으로 insert 처리가 된다

// 수정페이지로 이동 + 기존의 정보를 가져간다
    @GetMapping(value = "update.do")
    public ModelAndView updateGET(@RequestParam(name = "no") Long no){
        Board board = bRepository.findById(no).orElse(null);
        return new ModelAndView("board_update", "board", board);
    }

    // 수정하기
    @PostMapping(value="update.do")
    public String updatePOST(@ModelAttribute Board board) {
        System.out.println("===========board==============");
        System.out.println(board);

        Board board1 = new Board();
        board1.setNo(board.getNo());
        board1.setTitle(board.getTitle());
        board1.setContent(board.getContent());
        board1.setWriter(board.getWriter());
        Board ret = bRepository.save(board1);
        if(ret == null){
            return "redirect:/board/select.do";
        }
        return "redirect:/board/update.do";
    }

board_update.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>게시판 수정</title>
</head>

<body>
    <a th:href="@{/home.do}">홈으로</a>
    <hr />
    게시판수정
    <hr />
    
    <form th:action="@{/board/update.do}"  method="post">
        <input type="hidden" name="no" th:value="${board.no}" />
            <input type="text" th:field="${board.title}" name="title" placeholder="제목"  /><br />
            <input type="text" th:field="${board.content}" name="content" placeholder="내용" /><br />
            <input type="text" th:field="${board.writer}" name="writer" placeholder="작성자"  /><br />
            <br />
        <hr />
        <input type="submit" value="수정하기" />
    </form>
</body>

</html>

게시글 삭제

BoardController.java

bRepository.delete를 사용하여 기본키 정보가 있는 엔티티를 넣으면 삭제 가능

// 삭제하기
    @PostMapping(value = "/delete.do")
    public String deletePOST(
        @RequestParam(name = "no") Long no){
            try {
        // 반환타입이 없는것은 오류처리만 하면 된다
        // CrudRepository.deleteById(Long id) : void
                bRepository.deleteById(no);
                return "redirect:/board/select.do";
            } catch (Exception e) {
                return "redirect:/board/select.do";
            }
        }

board_select.html

<form th:action="@{/board/delete.do}" method="post">
                    <input type="hidden" name="no" th:value="${obj.no}" />
                    <input type="submit" value="삭제" />
                </form>

주문 페이지

생성되어있는 ORDER_VIEW 이용하여 주문페이지 조회
➡️ ORDER_VIEW는 조회만 가능하다!
생성된 VIEW의 데이터 수정/삭제는 원본 데이터 테이블에서만 가능

OrderRepository.java

@Repository
public interface OrderRepository extends JpaRepository<OrderView, Long>{
    
}

OrderController.java

package com.example.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.example.entity.OrderView;
import com.example.repository.OrderRepository;

@Controller
@RequestMapping(value = "/order")
public class OrderController {
    
    @Autowired
    OrderRepository oRepository;

    // 주문페이지 + 목록조회
    @GetMapping(value = "select.do")
    public ModelAndView SelectGET(){
        List<OrderView> list = oRepository.findAll();
        return new ModelAndView("order_select", "list", list);
    }

    // 삭제하기
    @PostMapping(value = "/delete.do")
    public String deletePOST(
        @RequestParam(name = "no") Long no){
            try {
        // 반환타입이 없는것은 오류처리만 하면 된다
        // CrudRepository.deleteById(Long id) : void
                oRepository.deleteById(no);
                return "redirect:/order/select.do";
            } catch (Exception e) {
                return "redirect:/order/select.do";
            }
        }

    // 수정페이지로 이동 + 기존의 정보를 가져간다
    @GetMapping(value = "update.do")
    public ModelAndView updateGET(@RequestParam(name = "no") Long no){
        OrderView order = oRepository.findById(no).orElse(null);
        return new ModelAndView("order_update", "order", order);
    }

    // 수정하기
    @PostMapping(value="update.do")
    public String updatePOST(@ModelAttribute OrderView order) {
        System.out.println("===========order==============");
        System.out.println(order);

        OrderView order1 = new OrderView();
        order1.setNo(order.getNo());
        order1.setAge(order.getAge());
        order1.setPhone(order.getPhone());
        order1.setGender(order.getGender());
        order1.setName(order.getName());
        OrderView ret = oRepository.save(order1);
        System.out.println("===========ret==============");
        System.out.println(ret);

        if(ret == null){
            return "redirect:/order/select.do";
        }
        return "redirect:/order/update.do";
    }
}

게시글에 댓글 작성

  • 한개의 게시글 하위에 여러개의 댓글이 생성된다
  • 댓글이 작성되기 위해서는 해당 게시글의 no가 필요하다

entity/Board.java

1개의 게시글 board 기준 = N개의 댓글 List<BoardReply>를 가질 수 있다

Board entity에 외래키를 설정해준다

    // mappedBy는 변수명
    @OneToMany(mappedBy = "board")
    private List<BoardReply> reply;

entity/BoardReply.java

N개의 댓글 List<BoardReply>은 1개의 게시글 board를 가질 수 있다

    // 외래키(다른 엔티티의 객체)   댓글(n)  <===> 게시글(1)
    @ManyToOne
    @JoinColumn(name = "BRDNO") // 외래키의 컬럼명
    private Board board;

BoardReplyRepository.java

@Repository
public interface BoardReplyRepository extends JpaRepository<BoardReply, Long>{
 
}

BoardReplyController.java

@Controller
@RequestMapping(value="/reply")
public class BoardReplyController {

	// 댓글페이지 접속하기
    @GetMapping(value="/insert.do")
    public ModelAndView insertGET(@RequestParam(name="no") Long no) {
        Board brd = new Board();
        brd.setNo(no); //기본키
        
        BoardReply obj = new BoardReply();
        obj.setBoard( brd);

        return new ModelAndView("reply_insert", "obj", obj);
    }


	// 댓글 등록하기
    @PostMapping(value="/insert.do")
    public String insertPOST(@ModelAttribute BoardReply breply) {
        System.out.println("===============breply=================");
        System.out.println(breply.toString());
        brRepository.save(breply);
        
        return "redirect:/board/selectone.do?no=" + breply.getBoard().getNo();
    }
}

board_selectone.html

BoardReply에는 Boardno가 외래키로 들어갔기 때문에 name="board.no"를 반드시 넘겨줘야 한다

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>게시글 상세</title>
</head>

<body>
    <a th:href="@{/home.do}">홈으로</a>
    <a th:href="@{/board/select.do}">글목록</a>
    <hr />
    게시판글쓰기
    <br />

    <a th:href="@{/board/select.do}" th:text="글목록"></a>
    <p th:text="${brd.no}"></p>
    <p th:text="${brd.title}"></p>
    <p th:text="${brd.content}"></p>
    <p th:text="${brd.writer}"></p>
    <p th:text="${brd.hit}"></p>
    <p th:text="${brd.regdate}"></p>
    <hr />

    <form th:action="@{/reply/insert.do}" method="post">
        <!-- board no가 외래키로 들어갔기 때문에 name="board.no"를 넘겨주기 -->
    <input type="text" name="board.no" th:value="${brd.no}" readonly /><br />
    <input type="text" name="content" placeholder="댓글내용" /><br />
    <input type="text" name="writer" placeholder="작성자" /><br />
    <input type="submit" placeholder="댓글달기" /><br />
</form>
    <hr />

    <table border="1">
        <tr th:each="obj, idx : ${reply}">
            <td th:text="${obj.no}"></td>
            <td th:text="${obj.content}"></td>
            <td th:text="${obj.writer}"></td>
            <td th:text="${obj.regdate}"></td>
            <td>
                <form th:action="@{/reply/delete.do}" method="post">
                    <input type="text" th:value="${obj.no}" name="no" />
                    <input type="text" th:value="${brd.no}" name="board.no"/>
                    <input type="submit" value="삭제" />
                </form>
            </td>
        </tr>
    </table>

</body>

</html>

0개의 댓글