가져오는 API의 url 형태에 따라 인증서 필요여부가 달라진다
- http = 인증서 필요없음
- https = 인증서 필요
➡️ 주소가 https로 시작하는경우 인증서를 무시하도록 설정해준다
인증서 처리를 위한 라이브러리 설치
<!-- 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>
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 라이브러리 설치
💡 mybatis 제외하고 설치
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
ddl-auto란 JPA에서 기본적으로 Entity에 테이블을 매핑하는 경우 쿼리를 사용하지 않고 값을 가져올 때 설정하는 옵션을 말한다
아래와 같은 옵션이 있다
환경설정
# 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
- entity 생성 후 서버 실행시 DB에 테이블이 생성된다
- entity 생성시에는 서버를 실행하지 말고 꺼뒀다가 완성 후 실행시 생성된다
- JPA는 엔티티와 데이터 매칭이 완벽하지 않으면 서버가 실행되지 않는다
@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;
}
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> {
}
// 게시글 작성페이지로 이동
@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";
}
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>
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);
}
<!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>
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";
}
<!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>
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";
}
}
<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의 데이터 수정/삭제는 원본 데이터 테이블에서만 가능
@Repository
public interface OrderRepository extends JpaRepository<OrderView, Long>{
}
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가 필요하다
1개의 게시글
board
기준 = N개의 댓글List<BoardReply>
를 가질 수 있다
Board entity에 외래키를 설정해준다
// mappedBy는 변수명
@OneToMany(mappedBy = "board")
private List<BoardReply> reply;
N개의 댓글
List<BoardReply>
은 1개의 게시글board
를 가질 수 있다
// 외래키(다른 엔티티의 객체) 댓글(n) <===> 게시글(1)
@ManyToOne
@JoinColumn(name = "BRDNO") // 외래키의 컬럼명
private Board board;
@Repository
public interface BoardReplyRepository extends JpaRepository<BoardReply, Long>{
}
@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();
}
}
BoardReply
에는Board
의no
가 외래키로 들어갔기 때문에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>