[홈페이지 제작] 게시판 만들기 1 - 게시글 표시, 페이지 넘기기

Feb 04, 2024
[홈페이지 제작] 게시판 만들기 1 - 게시글 표시, 페이지 넘기기
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());
notion image
 
 

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}} 반복문
 
💡
식별자는 기본키, 혹은 유니크만 올 수 있다. 외래키는 중복이 될 수 있어 식별자로 넣을 수 없다.
 
notion image
 

2. 게시판 페이지 넘기기

 
notion image
게시판에는 다음 페이지로 넘길 수 있는 버튼이 존재한다. 해당 버튼을 활성화 시켜보자.
 
@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 의 문법으로 {{}}을 통해 데이터를 전달받을 수 있다.
 
notion image
 
다음 페이지로 넘어갈 수 있다.
 
이제 가장 처음 페이지와 마지막 페이지 설정을 한다.
 
//가장 처음 페이지 설정 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 를 입력하게 되면 버튼이 비활성화 된다.
 
 
notion image
 
 
notion image
Share article
RSSPowered by inblog