파일명 : pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.4.Final</version>
</dependency>
파일명 application.properties
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
파일명 Boot20220406Application.java
package com.example.boot_20220406;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
// 정의 변수 설정
@PropertySource("classpath:global.properties")
@ServletComponentScan(basePackages = {"com.example.filter"})
// 컨트롤러, 환경설정파일
@ComponentScan(basePackages = {
"com.example.controller",
"com.example.restcontroller",
"com.example.config",
"com.example.service",
"com.example.jwt"
})
// 매퍼
@MapperScan(basePackages = {
"com.example.mapper"
})
// 엔티티(jpa) == DTO(mybatis)
@EntityScan(basePackages = {"com.example.entity"})
// 저장소(jpa) == 매퍼(mybatis)
@EnableJpaRepositories(basePackages = {"com.example.repository"})
public class Boot20220406Application {
public static void main(String[] args) {
SpringApplication.run(Boot20220406Application.class, args);
}
}
파일명 BoardEntity.java
package com.example.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;
@Entity
@Data
@Table(name = "BOARD10")
@SequenceGenerator(name = "SEQ_BOARD",
sequenceName = "SEQ_BOARD10_BNO",
allocationSize =1,
initialValue = 1)
public class BoardEntity {
@Id // 기본키
@Column(name = "BNO") // 컬럼명
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_BOARD") // 시퀀스 적용
private Long no; // 타입 변수(NUMBER)
@Column(name = "BTITLE", length = 200) //VARCHAR2(200)
private String title;
@Lob //CLOB
@Column(name = "BCONTENT")
private String content;
@Column(name = "BWRITER", length = 100)
private String writer;
@Column(name = "BHIT")
private Long hit = 1L;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@CreationTimestamp //CURRENT_DATE
@Column(name = "BREGDATE")
private Date regdate;
}
파일명 BoardRepository.java
package com.example.repository;
import java.util.List;
import com.example.entity.BoardEntity;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface BoardRepository extends JpaRepository<BoardEntity, Long> {
// 검색어가 포함된 전체 개수
// SELECT COUNT(*) FROM BOARD10
// WHERE BTITLE LIKE '%' || '검색어' || '%'
long countByTitleContaining(String title);
List<BoardEntity> findByTitle(String title);
// findBy컬럼명Containing
// WHERE TITLE LIKE '%' || '검색어' || '%'
List<BoardEntity> findByIgnoreCaseTitleContaining(String title);
// findBy컬럼명ContainingOrderBy컬렴명Desc
// SELECT * FROM 테이블명
// WHERE TITLE LIKE '%' || '검색어' || '%' ORDER BY NO DESC
List<BoardEntity> findByTitleContainingOrderByNoDesc(String title);
// SELECT B.*, ROW_NUMBER() OVER( ORDER BY BNO DESC ) FROM BOARD10 B
// WHERE BTITLE LIKE '%' || '검색어' || '%'
List<BoardEntity> findByTitleContainingOrderByNoDesc(String title, Pageable pageable);
@Query(value =
" SELECT * FROM BOARD10 B WHERE BTITLE LIKE %:ti% ", nativeQuery = true)
List<BoardEntity> selectBoardList(@Param(value = "ti") String title);
// 이전글 ex) 20번 이면 작은덧 중에서 가장큰것 1개 19...
BoardEntity findTop1ByNoLessThanOrderByNoDesc(long no);
// 다음글 ex) 20번 이면 큰것중에서 가장 작은것 1개 21...
BoardEntity findTop1ByNoGreaterThanOrderByNoAsc(long no);
}
파일명 BoardController.java
package com.example.controller;
import java.util.List;
import com.example.entity.BoardEntity;
import com.example.repository.BoardRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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;
@Controller
@RequestMapping(value = "/board")
public class BoardController {
@Autowired BoardRepository bRepository;
@Value("${board.page.count}") int PAGECNT;
@GetMapping(value = "/insert")
public String insertGET(){
return "board_insert";
}
@PostMapping(value = "/insert")
public String insertPOST(
@ModelAttribute BoardEntity board
){
// save(entity객체) == INSERT INTO
bRepository.save(board);
return "redirect:/board/selectlist";
}
@GetMapping(value = "selectlist")
public String selectlistGET(
Model model,
@RequestParam(name = "page", defaultValue = "1")int page,
@RequestParam(name = "title", defaultValue = "")String title
){
// 페이지네이션(시작페이지(0부터), 개수)
PageRequest pageRequest = PageRequest.of(page-1,PAGECNT);
// findAll == SELECT * FROM 테이블;
// 검색어, 페이지네이션
List<BoardEntity> list = bRepository.findByTitleContainingOrderByNoDesc(title , pageRequest);
model.addAttribute("list", list);
// 10 => 1 , 23 => 3 , 45 => 5
long total = bRepository.countByTitleContaining(title);
model.addAttribute("pages", (total -1)/PAGECNT+1);
return "board_selectlist";
}
@GetMapping(value = "/selectone")
public String selectOneGET(
Model model,
@RequestParam(name = "no")long no
){
BoardEntity board = bRepository.findById(no).orElse(null);
// System.out.println("================================");
// System.out.println(no);
// System.out.println(board.toString());
model.addAttribute("board",board);
return "board_selectone";
}
@GetMapping(value = "/selectnext")
public String selectNextGET(
Model model,
@RequestParam(name = "no")long no
){
BoardEntity board = bRepository.findTop1ByNoGreaterThanOrderByNoAsc(no);
// System.out.println("================================");
// System.out.println(board);
if(board != null){
model.addAttribute("board",board);
return "board_selectone";
}
return "redirect:/board/selectone?no=" + no;
}
@GetMapping(value = "/selectpre")
public String selectpreGET(
Model model,
@RequestParam(name = "no")long no
){
BoardEntity board = bRepository.findTop1ByNoLessThanOrderByNoDesc(no);
// System.out.println("================================");
// System.out.println(board);
if(board != null){
model.addAttribute("board",board);
return "board_selectone";
}
return "redirect:/board/selectone?no=" + no;
}
}
파일명 BoardRestController.java
// 게시물의 조회수 1증가 시킴
// 127.0.0.1:9090/ROOT/api/board/updatehit1?bno=2
@RequestMapping(
value = "/updatehit1",
method = { RequestMethod.PUT },
consumes = { MediaType.ALL_VALUE },
produces = { MediaType.APPLICATION_JSON_VALUE})
public Map<String, Object> boardUpdateHit1PUT(
@RequestParam(name = "no") long no
){
Map<String, Object> map = new HashMap<>();
try{
BoardEntity board = bRepository.findById(no).orElse(null);
board.setHit(board.getHit()+1L);
bRepository.save(board);
map.put("status",200);
}
catch(Exception e){
map.put("status",0);
}
return map;
}
파일명 board_insert.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 style="padding: 10px;">
<h3>게시판 글쓰기 페이지 입니다.</h3>
<hr />
<div style="padding: 20px;">
<form th:action="@{/board/insert}" method="post" >
<label style="width:75px; height: 30px; display:inline-block;">제목 : </label>
<input type="text" placeholder="제목" name="title"/></br>
<label style="width:75px; height: 30px; display:inline-block;">작성자 : </label>
<input type="text" placeholder="작성자" name="writer"/></br>
<label style="width:75px; height: 30px; display:inline-block;">내용 : </label>
<textarea cols="30" rows="10" placeholder="내용" name="content"></textarea><br />
<label style="width:75px; height: 30px; display:inline-block;"></label>
<input type="submit" value="글등록" />
</form>
</div>
</body>
</html>
파일명 board_selectlist.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 style="padding: 10px;">
<h3>게시판화면</h3>
<hr />
<div style="padding: 10px;">
<a th:href="@{/board/insert}">글쓰기</a>
<form th:action="@{/board/selectlist}" method="get">
<input type="hidden" value="1" name="page" />
<input type="text" name="title" placeholder="검색어" />
<input type="submit" value="검색" />
</form>
<table border="1">
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>조회수</th>
<th>등록일</th>
</tr>
<tr th:each="brd : ${list}">
<td th:text="${brd.no}"></td>
<td><a href="#" th:onclick="|javascript:updateHit('${brd.no}')|"
th:text="${brd.title}"></a></td>
<td th:text="${brd.writer}"></td>
<td th:text="${brd.hit}"></td>
<td th:text="${brd.regdate}"></td>
</tr>
</table>
<th:block th:each="i : ${#numbers.sequence(1, pages)}">
<a th:href="@{/board/selectlist(page=${i}, txt=${param.txt})}" th:text="${i}"></a>
</th:block>
</div>
<script>
function updateHit(no){
// alert(no); 확인용
// 1. 조회수 증가용 rest ful 호출 ex) acios
const xhr = new XMLHttpRequest(); // ex) axios와 같은것
console.log(xhr);
const url = "/ROOT/api/board/updatehit1?no="+ no;
xhr.open("PUT", url, true);
xhr.responseType = "json";
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.onload = function(e){
console.log(e.target);
if(e.target.response.status === 200 ){
// 2. 다음 페이지 이동
location.href="/ROOT/board/selectone?no=" + no;
}
}
xhr.send(); // 호출해야 onload가 반응함
}
</script>
</body>
</html>
파일명 board_selectone.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 style="padding: 10px;">
<h3>게시판 상세</h3>
<hr />
<div style="padding:20px">
<hr />
<label style="width:75px; height: 30px; display:inline-block;">글번호 :</label>
<P style="display:inline-block" th:text="${board.no}"></P><br />
<label style="width:75px; height: 30px; display:inline-block;">제목 :</label>
<P style="display:inline-block" th:text="${board.title}"></P><br />
<label style="width:75px; height: 30px; display:inline-block;">작성자 :</label>
<P style="display:inline-block" th:text="${board.writer}"></P><br />
<label style="width:75px; height: 30px; display:inline-block;">내용 :</label>
<P style="display:inline-block" th:text="${board.content}"></P><br />
<label style="width:75px; height: 30px; display:inline-block;">조회수 :</label>
<P style="display:inline-block" th:text="${board.hit}"></P><br />
<label style="width:75px; height: 30px; display:inline-block;">등록일 :</label>
<P style="display:inline-block" th:text="${board.regdate}"></P><br />
<hr />
<a th:href="@{/board/selectlist}"><button>목록으로</button></a>
<a th:href="@{/board/selectpre(no=${board.no})}"><button>이전글</button></a>
<a th:href="@{/board/selectnext(no=${board.no})}"><button>다음글</button></a>
<button>수정</button>
<button>삭제</button>
</div>
</body>
</html>