shop.mtcoding.blog/board/Board
package shop.mtcoding.blog.board;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
import java.time.LocalDate;
@Data //게터세터,toString
@Entity // entity로 만든 것만 파싱함.
@Table(name="board_tb") // 테이블명
public class Board {
@Id // 프라이머리키 설정
@GeneratedValue(strategy = GenerationType.IDENTITY) //auto_increment
//포링키 테이블에 제약조건은 안넣는게 좋다. 삭제할 때 문제 생김
private int id ;
private String title;
private String content;
private int userId ; // 포링키 , 포링키에
//타입이 스칼라가 아닌 여러개면 쪼개야됨.
@CreationTimestamp
private LocalDateTime createdAt ;
}
shop.mtcoding.blog/board/BoardController
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import shop.mtcoding.blog.user.User;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.List;
@RequiredArgsConstructor
@Controller
public class BoardController {
private final HttpSession session;
private final BoardRepository boardRepository ;
@GetMapping({ "/", "/board" })
public String index(HttpServletRequest request , @RequestParam(defaultValue = "0") int page) {
System.out.println("페이지"+page);
List<Board> boardList = boardRepository.findAll(page);
request.setAttribute("boardList",boardList); // 가방에 담기
int curruentPage =page ;
int nextPage = curruentPage +1 ;
int prevPage = curruentPage -1 ;
request.setAttribute("nextPage",nextPage); // 가방에 담기
request.setAttribute("prevPage",prevPage); // 머스태치에 담기만 하면 됨
int totalCount = 4; // 나중에는 db에서 전체 데이터를 조회해야 됨. 그래야 라스트페이지를 알 수 있음
// total = 4 cp = 0 false
// total = 4 cp = 1 ture
int paging = 0 ;
boolean first = (curruentPage==0 ?true :false);
request.setAttribute("first",first);
int totalPage = totalCount/3 ;
if(totalCount%3==0){
int lastPage = totalPage -1 ;
boolean last = (curruentPage==lastPage? true:false);
request.setAttribute("last",last);
} else if(totalCount%3!=0){
int lastPage = totalPage ;
boolean last = (curruentPage==lastPage? true:false);
request.setAttribute("last",last);
}
return "index";
}
@GetMapping("/board/saveForm")
public String saveForm() {
return "board/saveForm";
}
@GetMapping("/board/1")
public String detail() {
return "board/detail";
}
}
shop.mtcoding.blog/board/BoardRepository
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.math.BigInteger;
import java.util.List;
@RequiredArgsConstructor
@Controller
public class BoardRepository {
private final EntityManager em ;
public int count(){
Query query = em.createNativeQuery("select count(*) from board_tb");
BigInteger count = (BigInteger) query.getSingleResult();
return count.intValue();
}
public List<Board> findAll(int page){
final int COUNT = 3;
int value = page * COUNT ;
Query query = em.createNativeQuery("select * from board_tb order by id desc limit ?,?",Board.class); // 한 페이지에 3개씩 뿌림
query.setParameter(1,value);
query.setParameter(2,COUNT);
List<Board> boardList = query.getResultList();
return boardList;
}
}
resources/templates/user/index.mustache
{{> layout/header}}
<!--머스태치 문법/ 이렇게 적으면 헤더를 매번 포함할 수 있음-->
<!--리퀘스트에 있는걸 꺼내옴 템플릿 엔진은 리퀘스트에 담음. 데이터르 화면에 전달한게 아님
데이터는 가방에 담음. 화면은 가방에 있는걸 꺼냄.
오브젝트면 if문이 되고, 컬렉션일 떄는 for 문으로 바뀜-->
{{#boardList}}
<div class="card mb-3">
<div class="card-body">
<h4 class="card-title mb-3">{{title}}</h4>
<a href="/board/{{id}}" class="btn btn-primary">상세보기</a>
</div>
</div>
{{/boardList}}
<div class="container p-5">
<ul class="pagination d-flex justify-content-center">
<li class="page-item {{#first}}disabled{{/first}} "><a class="page-link" href="?page={{prevPage}}">Previous</a></li>
<li class="page-item {{#last}}disabled{{/last}} "><a class="page-link" href="?page={{nextPage}}">Next</a></li>
</ul>
</div>
{{> layout/footer}}
resources/application-dev.yml
server: servlet: encoding: charset: utf-8 force: true session: timeout: 30m port: 8080 spring: datasource: driver-class-name: org.h2.Driver url : jdbc:h2:mem:test;MODE=MySQL username : sa password : mustache: servlet: expose-session-attributes: true expose-request-attributes: true # 머스터치에서 세션과 리퀘스트에 접근할 수 있도록 하는 코드 #db 연결 h2: console: enabled: true sql: init: data-locations: - classpath:db/data.sql # 클레스패스는 리소스폴더, data-locations 는 리스트타입/ 야물 문법 #웹에 연결될 수 있게 jpa: hibernate: ddl-auto: create show-sql: true properties: hibernate: format_sql: true defer-datasource-initialization: true #서버가 실행될 때 @entity 되어있는걸 크리에이트함. #hibernate 가 실행될 때 show-sql: true 면 내용 띄워줌
이전 내용은 아래 블로그에서 확인할 수 있다.
application-dev.yml
sql: init: data-locations: - classpath:db/data.sql // 해당 경로의 데이터를 DB에 초기화시킴 jpa: hibernate: ddl-auto: create show-sql: true properties: hibernate: format_sql: true defer-datasource-initialization: true //데이터 소스 초기화가 애플리케이션 시작 시점이 아니라, //실제로 데이터베이스 연결이 필요한 순간에 이루어짐
application-dev.yml 에 코드를 입력한다. data.sql 의 쿼리문을 더미데이터로 만들어준다.
insert into user_tb(username, password, email, created_at) values('ssar', '1234', 'ssar@nate.com', now()); insert into user_tb(username, password, email, created_at) values('cos', '1234', 'cos@nate.com', now()); insert into board_tb(title,content,user_id,created_at) values ('제목1','내용1',1,now()); insert into board_tb(title,content,user_id,created_at) values ('제목2','내용2',1,now()); insert into board_tb(title,content,user_id,created_at) values ('제목3','내용3',1,now()); insert into board_tb(title,content,user_id,created_at) values ('제목4','내용4',2,now());

1. 게시판 테이블 만들기
페이지의 로직을 정리하면 다음과 같다. 클라이언트가 입력한 URL을 컨트롤러가 받는다. 컨트롤러는 유효성 검사 하고 View를 연결하거나 , 레파지토리에 요청해서 DB를 가져온다. 가져온 데이터는 HttpRequestServlet 객체에 담는다. 객체를 템플릿 엔진에 주면 템플릿 엔진이 하나씩 꺼내서 화면에 출력한다.
(1) 게시판 클래스 만들기 (shop.mtcoding.blog/board/Board)
@Data //게터세터,toString
@Entity // entity로 만든 것만 파싱함.
@Table(name="board_tb") // 테이블명
public class Board {
@Id // 프라이머리키 설정
@GeneratedValue(strategy = GenerationType.IDENTITY) //auto_increment
//포링키 테이블에 제약조건은 안넣는게 좋다. 삭제할 때 문제 생김
private int id ;
private String title;
private String content;
private int userId ; // 포링키 , 포링키에
//타입이 스칼라가 아닌 여러개면 쪼개야됨.
@CreationTimestamp
private LocalDate createdAt ;
}
게시판을 만들기 위해서 필요한 요소들을 정한다. id 는 primary key 로 자동으로 증가하는 테이블 번호이다.
@Table(name="board_tb")
는 board_tb
과 Board 클래스가 매핑될 수 있도록 도와준다.(2) URL 입력받기 (shop.mtcoding.blog/board/BoardController)
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import shop.mtcoding.blog.user.User;
import javax.servlet.http.HttpSession;
import java.util.List;
@RequiredArgsConstructor
@Controller
public class BoardController {
private final HttpSession session; // 세션
private final BoardRepository boardRepository ; // 의존성 주입
@GetMapping({ "/", "/board" })
public String index(HttpServletRequest request,@RequestParam(defaultValue = "0") int page) {
List<Board> boardList = boardRepository.findAll(page);
request.setAttribute("boardList",boardList); // request에 담기
return "index";
}
@GetMapping("/board/saveForm")
public String saveForm() {
return "board/saveForm";
}
@GetMapping("/board/1")
public String detail() {
return "board/detail";
}
}
지금 만들 페이지는 게시판이 기본 페이지로 설정되어 있다. 따라서 localhost:8080/ 를 입력하거나,
localhost:8080/index 를 입력해도 연결될 수 있도록 한다.
(3) DB연결하기 (shop.mtcoding.blog/board/BoardRepository)
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import javax.persistence.Query;
import java.math.BigInteger;
import java.util.List;
@RequiredArgsConstructor
@Repository
public class BoardRepository {
private final EntityManager em ; // 의존성 주입
public List<Board> findAll(int page){ // 쿼리문 날리기.
final int COUNT = 3;
int value = page * COUNT ;
Query query = em.createNativeQuery("select * from board_tb order by id desc limit ?,?",Board.class); // 한 페이지에 3개씩 뿌림
query.setParameter(1,value);
query.setParameter(2,COUNT);
List<Board> boardList = query.getResultList();
return boardList;
}
}
findAll 메서드는 화면에 출력될 게시글의 수를 나타낸다. select 쿼리문을 통해 가장 최신 글 부터 3개씩 표시되도록 만들었다. DB와 통신을 통해서 받은 데이터는 View 에 전달된다.
(4) 화면 출력 (resources/templates/user/index.mustache)
{{> layout/header}}
{{#boardList}}
<div class="card mb-3">
<div class="card-body">
<h4 class="card-title mb-3">{{title}}</h4>
<a href="/board/{{id}}" class="btn btn-primary">상세보기</a>
<!--식별자는 프라이머리키 아니면 유니크, 포링키는 중복될 수 있어서 안됨 -->
</div>
</div>
{{/boardList}}
<div class="container p-5">
<ul class="pagination d-flex justify-content-center">
<li class="page-item} "><a class="page-link" href="">Previous</a></li>
<li class="page-item} "><a class="page-link" href="">Next</a></li>
</ul>
</div>
{{> layout/footer}}
mustache 의 문법을 알아야 한다. mustache 에서는 request 객체를 받는다. 여기서는 List 타입의 boardList를 전달받았다. mustache 의 문법 중 {{#request명}} {{/request명}}
문법이 있다. 만약 전달받은 request 가 오브젝트 타입이라면 {{#request명}} {{/request명}} 는 if 문이 되고, 컬렉션 타입이라면 for문으로 사용된다. 현재는 List타입이기 때문에 for 반복문으로 사용된다.
mustache 문법은 추후에 따로 정리하기로 한다.
오브젝트일 때 : {{#request명}} {{/request명}} if문, {{^request명}} {{/request명}} else 문
컬렉션일 때 : {{#request명}} {{/request}} 반복문
식별자는 기본키, 혹은 유니크만 올 수 있다. 외래키는 중복이 될 수 있어 식별자로 넣을 수 없다.

2. 게시판 페이지 넘기기

게시판에는 다음 페이지로 넘길 수 있는 버튼이 존재한다. 해당 버튼을 활성화 시켜보자.
@GetMapping({ "/", "/board" })
public String index(HttpServletRequest request , @RequestParam(defaultValue = "0") int page) {
System.out.println("페이지"+page);
List<Board> boardList = boardRepository.findAll(page);
request.setAttribute("boardList",boardList);
int curruentPage =page ;
int nextPage = curruentPage +1 ;
int prevPage = curruentPage -1 ;
request.setAttribute("nextPage",nextPage);
request.setAttribute("prevPage",prevPage);
return "index";
}
@RequestParam(defaultValue = "0") 이 코드를 통해 /?page=0 를 입력하지 않아도 0페이지로 연결되도록 한다.
그리고 현재 페이이지를 정하고, 전 후 페이지 값을 정한다.
setAttribute 를 통해 request 객체를 View에 전달한다.
<ul class="pagination d-flex justify-content-center">
<li class="page-item } "><a class="page-link" href="?page={{prevPage}}">Previous</a></li>
<li class="page-item } "><a class="page-link" href="?page={{nextPage}}">Next</a></li>
</ul>
href="?page={{prevPage}} 에 객체를 전달받는다. mustache 의 문법으로 {{}}을 통해 데이터를 전달받을 수 있다.

다음 페이지로 넘어갈 수 있다.
이제 가장 처음 페이지와 마지막 페이지 설정을 한다.
//가장 처음 페이지 설정
boolean first = (curruentPage==0 ?true :false);
request.setAttribute("first",first);
//가장 마지막 페이지 설정
int totalCount = 4; // 나중에는 db에서 전체 데이터를 조회해야 됨. 그래야 라스트페이지를 알 수 있음
int totalPage = totalCount/3 ;
if(totalCount%3==0){
int lastPage = totalPage -1 ;
boolean last = (curruentPage==lastPage? true:false);
request.setAttribute("last",last);
} else if(totalCount%3!=0){
int lastPage = totalPage ;
boolean last = (curruentPage==lastPage? true:false);
request.setAttribute("last",last);
}
첫번째 페이지는 0 페이지를 기준으로 true 값을 전달한다.
마지막 페이지는 한 페이지에 3개씩 표시하기 때문에 전체 게시글의 수를 3으로 나눈다.
만약 나눈 나머지가 0이라면 ( ex. 게시글 6개/3 = 2 ) 나눈 몫이 페이지 수가 되며, 마지막 페이지는 -1 의 값인 page=1 이 된다.
반대로 나눈 값이 0이 아니라면 (ex. 게시글 5개/3 = 1.xx) 이기 때문에 몫의 +1 이 페이지 게수가 되며, 마지막 페이지는 몫의 값 page =1 을 갖는다.
<ul class="pagination d-flex justify-content-center">
<li class="page-item {{#first}}disabled{{/first}} "><a class="page-link" href="?page={{prevPage}}">Previous</a></li>
<li class="page-item {{#last}}disabled{{/last}} "><a class="page-link" href="?page={{nextPage}}">Next</a></li>
</ul>
View에서 request 값을 전달 받는다. 이때 {{#}}disabled{{/}} 이렇게 disabled 를 입력하게 되면 버튼이 비활성화 된다.


Share article