1. 글 쓰기
- 글을 작성했을 때 Relation Mapping을 통해 쿼리 없이 자동으로 DB에 데이터가 들어가도록 설정
- BoardController
- 글을 작성하고 글쓰기완료 버튼을 누르면 session 정보와 DTO를 전송
@GetMapping("/board/save-form")
public String saveForm(HttpSession session) {
User validatedUser = (User) session.getAttribute("validatedUser");
if (validatedUser == null) throw new RuntimeException("인증이 필요합니다.");
return "board/save-form";
}
@PostMapping("/board/save")
public String save(BoardRequest.SaveDTO saveDTO, HttpSession session) {
User validatedUser = (User) session.getAttribute("validatedUser");
if (validatedUser == null) throw new RuntimeException("인증이 필요합니다.");
boardService.글쓰기(saveDTO, validatedUser);
return "redirect:/";
}
- BoardRequest
- user 정보를 세션에서 받아옴
@Data
public static class SaveDTO {
private String title;
private String content;
private String isPublic;
public Board toEntity(User user) {
return Board.builder()
.title(title)
.content(content)
.isPublic(isPublic == null ? false : true)
.user(user)
.build();
}
}
- BoardService
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User user) {
Board board = saveDTO.toEntity(user);
boardRepository.write(board);
}
- BoardRepository
public void write(Board board) {
em.persist(board);
}
- board/save-form.mustache
{{> layout/header}}
<div class="container p-5">
<div class="card">
<div class="card-header"><b>글쓰기 화면입니다</b></div>
<div class="card-body">
<form action="/board/save" method="post">
<div class="mb-3">
<input type="text" class="form-control" placeholder="Enter title" name="title">
</div>
<div class="mb-3">
<textarea class="form-control" rows="5" name="content"></textarea>
</div>
<!-- ✅ 공개 여부 체크박스 -->
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="isPublic">
<label class="form-check-label" for="isPublic">
공개 글로 작성하기
</label>
</div>
<button class="btn btn-primary form-control">글쓰기완료</button>
</form>
</div>
</div>
</div>
{{> layout/footer}}
결과
- 글을 작성하면 목록 갱신


2. 글 목록
- BoardController
- 작성된 글 목록을 List로 받아 폼에 출력
- 비공개 글은 다른 사람에게 보이면 안되고, 내가 쓴 글은 비공개여도 보여야 함
- 세션 정보를 받아 userId의 여부에 따라 다른 글 목록 표시
@GetMapping("/")
public String home(HttpServletRequest request, HttpSession session) {
User validatedUser = (User) session.getAttribute("validatedUser");
if (validatedUser == null) {
request.setAttribute("models", boardService.글목록보기(null));
} else {
request.setAttribute("models", boardService.글목록보기(validatedUser.getId()));
}
return "board/list";
}
- BoardService
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
- BoardRepository
- userId가 null인지 여부에 따라 2가지의 sql 작동
- userId가 null이 아니면 (로그인했으면) 자신의 글을 보여주어야 함
- where b.isPublic = true or b.user.id = :userId
public List<Board> findAll(Integer userId) {
String s1 = "select b from Board b where b.isPublic = true or b.user.id = :userId order by b.id desc";
String s2 = "select b from Board b where b.isPublic = true order by b.id desc";
Query q = em.createQuery(userId == null ? s2 : s1, Board.class);
if(userId != null) q.setParameter("userId", userId);
return q.getResultList();
}
- board/list.mustache
{{> layout/header}}
<div class="container p-5">
<div class="mb-3 d-flex justify-content-end">
<form class="d-flex">
<input class="form-control me-2" type="text" placeholder="검색...">
<button class="btn btn-primary" type="button">Search</button>
</form>
</div>
{{#models}}
<div class="card mb-3">
<div class="card-body">
<div class="d-flex justify-content-between">
<h4 class="card-title mb-3">{{title}}</h4>
{{^isPublic}}
<div class="d-flex align-items-start">
<span class="badge bg-secondary">비공개</span>
</div>
{{/isPublic}}
</div>
<a href="/board/{{id}}" class="btn btn-primary">상세보기</a>
</div>
</div>
{{/models}}
<ul class="pagination d-flex justify-content-center">
<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul>
</div>
{{> layout/footer}}
Test
- BoardRepositoryTest
@Autowired // DI
private BoardRepository boardRepository;
@Test
public void findAll_test() {
// given
// when
List<Board> boardList = boardRepository.findAll();
// Lazy -> Board -> User(id=1)
// Eager -> N+1 -> Board조회 -> 연관된 User 유저 수 만큼 주회
// Eager -> Join -> 한방쿼리
System.out.println("--------------------");
boardList.get(0).getUser().getUsername();
System.out.println("--------------------");
// eye
}
- findAll()의 동작을 확인하기 위한 test
- user의 select 쿼리가 3번 발동함
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.is_public,
b1_0.title,
b1_0.user_id
from
board_tb b1_0
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
user_tb u1_0
where
u1_0.id=?
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
user_tb u1_0
where
u1_0.id=?
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
user_tb u1_0
where
u1_0.id=?
- 글을 작성한 user = 3명
- 4번이 아닌 이유? PC에 캐싱
insert into board_tb(title, content, user_id, is_public, created_at)
values ('제목1', '내용1', 1, true, now());
insert into board_tb(title, content, user_id, is_public, created_at)
values ('제목2', '내용2', 1, true, now());
insert into board_tb(title, content, user_id, is_public, created_at)
values ('제목3', '내용3', 2, true, now());
insert into board_tb(title, content, user_id, is_public, created_at)
values ('제목4', '내용4', 3, true, now());
- user가 1명일 경우
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.is_public,
b1_0.title,
b1_0.user_id
from
board_tb b1_0
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
user_tb u1_0
where
u1_0.id=?
- FetchType = LAZY
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.is_public,
b1_0.title,
b1_0.user_id
from
board_tb b1_0
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.is_public,
b1_0.title,
b1_0.user_id
from
board_tb b1_0
--------------------
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
user_tb u1_0
where
u1_0.id=?
--------------------
Lazy vs Eager
Lazy 전략 : 릴레이션 매핑을 사용하지 않음
Eager 전략 : 무조건 릴레이션 매핑을 사용함
Eager의 경우 불필요한 데이터를 가져올 수 있기 때문에, Lazy 전략을 사용하되 릴레이션 매핑이 필요하면 직접 쿼리를 작성
결과
- 로그인 안 했을 때

- ssar로 로그인 했을 때 (비공개 글)

3. 글 상세보기
- BoardController
- 글을 작성하고 글쓰기완료 버튼을 누르면 session 정보와 DTO를 전송
@GetMapping("/board/{id}")
public String detailForm(@PathVariable("id") int id, HttpSession session, HttpServletRequest request) {
Integer userId = null;
User validatedUser = (User) session.getAttribute("validatedUser");
if(validatedUser != null) userId = validatedUser.getId();
BoardResponse.DetailDTO board = boardService.글상세보기(id, userId);
request.setAttribute("model", board);
return "board/detail";
}
- BoardResponse
- 글쓴이와 로그인한 유저가 일치해야 수정, 삭제 표시가 가능
- isOwner 속성 추가
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isPublic;
private Boolean isOwner;
private Integer userId;
private String username;
private Timestamp createdAt;
public DetailDTO(Board board, Integer sessionUserId) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.isPublic = board.getIsPublic();
this.isOwner = board.getUser().getId() == sessionUserId;
this.userId = board.getUser().getId();
this.username = board.getUser().getUsername();
this.createdAt = board.getCreatedAt();
}
}
- BoardService
public BoardResponse.DetailDTO 글상세보기(int id, Integer sessionUserId) {
BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO(boardRepository.findByIdJoinUser(id), sessionUserId);
return detailDTO;
}
- BoardRepository
public Board findByIdJoinUser(int id) {
// inner join은 on 절 생략 가능
Query q = em.createQuery("select b from Board b join fetch b.user where b.id = :id", Board.class);
q.setParameter("id", id);
return (Board) q.getSingleResult();
}
- board/detail.mustache
{{> layout/header}}
<div class="container p-5">
{{#model.isOwner}}
<!-- 수정삭제버튼 -->
<div class="d-flex justify-content-end">
<a href="/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{model.content}}
</div>
</div>
<!-- AJAX 좋아요 영역 -->
<div class="my-3 d-flex align-items-center">
<span class="my-like-heart" onclick="likeToggle()">❤</span>
<span><b id="likeCount">12</b>명이 이 글을 좋아합니다</span>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/reply/save" method="post">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">cos</div>
<div>댓글 내용입니다</div>
</div>
<form action="/reply/1/delete" method="post">
<button class="btn">🗑</button>
</form>
</div>
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">ssar</div>
<div>댓글 내용입니다</div>
</div>
<form action="/reply/1/delete" method="post">
<button class="btn">🗑</button>
</form>
</div>
</div>
</div>
</div>
<script>
function likeToggle() {
let likeHeart = document.querySelector('.my-like-heart');
console.log(likeHeart);
likeHeart.classList.toggle('liked');
}
</script>
{{> layout/footer}}
Test
- BoardRepositoryTest
@Test
public void findByIdJoinUser_test() {
// given
int id = 1;
// when
Board board = boardRepository.findByIdJoinUser(id);
// eye
System.out.println(board);
}
- join fetch
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.is_public,
b1_0.title,
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
board_tb b1_0
join
user_tb u1_0
on u1_0.id=b1_0.user_id
where
b1_0.id=?
Board{id=1, title='제목1', content='내용1', isPublic=true, user=User{id=1, username='ssar', password='1234', email='ssar@nate.com', createdAt=2025-04-07 14:42:21.914297}, createdAt=2025-04-07 14:42:21.91629}
- join
- select 2회 실행
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.is_public,
b1_0.title,
b1_0.user_id
from
board_tb b1_0
join
user_tb u1_0
on u1_0.id=b1_0.user_id
where
b1_0.id=?
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
user_tb u1_0
where
u1_0.id=?
Board{id=1, title='제목1', content='내용1', isPublic=true, user=User{id=1, username='ssar', password='1234', email='ssar@nate.com', createdAt=2025-04-07 14:44:58.553253}, createdAt=2025-04-07 14:44:58.555246}
join vs join fetch
join : select가 2회 실행 (FetchType = LAZY)
join fetch : join된 쿼리문이 1회만 실행
결과
- 로그인 안 했을 때
- 수정, 삭제 버튼이 보이지 않음

- 로그인 했을 때 1
- 자신의 글은 수정, 삭제 버튼 확인 가능

- 로그인 했을 때 2
- 다른 사람의 글은 수정, 삭제 버튼 확인 불가

4. 글 삭제
- BoardController
@PostMapping("/board/{id}/delete")
public String delete(@PathVariable("id") Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new Exception401("인증이 필요합니다");
boardService.글삭제(id);
return "redirect:/";
}
- BoardService
@PostMapping("/board/{id}/delete")
public String delete(@PathVariable("id") Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
boardService.글삭제(id, sessionUser.getId());
return "redirect:/";
}
- BoardRepository
- em.remove : 영속성을 활용하여 cascade로 연결되어 있는 객체들까지 한 번에 삭제 가능
- em.remove(em.find(Board.class, id)) : id가 일치하는 board의 연관 객체들 전부 삭제
- delete cascade from board_tb where id = ‘id’;
public void deleteById(Integer id) {
em.remove(em.find(Board.class, id));
}
5. 글 수정
- BoardController
@GetMapping("/board/{id}/update-form")
public String updateForm(@PathVariable("id") Integer id, BoardRequest.UpdateDTO updateDTO, HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new Exception401("인증이 필요합니다");
Board boardPS = boardService.글수정화면(updateDTO, id);
request.setAttribute("model", boardPS);
return "board/update-form";
}
@PostMapping("/board/{id}/update")
public String update(@PathVariable("id") Integer id, BoardRequest.UpdateDTO updateDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new Exception401("인증이 필요합니다");
boardService.글수정(updateDTO, sessionUser.getId(), id);
return "redirect:/board/"+id;
}
- BoardRequest
- isPublic은 on 또는 null 값이 리턴됨
- true/false 값을 받아야 하므로 이를 체크할 수 있는 isPublicChecked() 메서드 추가
- String인 isPublic이 on이면 true, 아니면 false
@Data
public static class UpdateDTO {
private String title;
private String content;
private String isPublic; // "on" or null
public Boolean isPublicChecked() {
return "on".equals(isPublic);
}
}
- BoardService
- Update는 Dirty Checking을 사용하여 board 객체 내부 값을 setter로 변경
- Transaction이 끝나면 자동 update
public Board 글수정화면(BoardRequest.UpdateDTO updateDTO, Integer id) {
return boardRepository.findByIdJoinUserAndReplies(id);
}
@Transactional
public void 글수정(BoardRequest.UpdateDTO updateDTO, Integer userId, Integer boardId) {
// 1. 글 불러오기 by boardId
Board board = boardRepository.findById(boardId);
// 1-1. 글이 없으면 Exception404
if(board == null) throw new Exception404("해당하는 글이 없습니다.");
// 2. 글 주인과 userId 비교 : 불일치 시 Exception403
if(!(board.getUser().getId().equals(userId))) throw new Exception403("권한이 없습니다.");
// 3. update (dirty checking)
board.update(updateDTO.getTitle(), updateDTO.getContent(), updateDTO.isPublicChecked());
}
- BoardRepository
public Board findByIdJoinUserAndReplies(Integer id) {
Query query = em.createQuery("select b from Board b join fetch b.user left join fetch b.replies r left join fetch r.user where b.id = :id order by r.id desc", Board.class);
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
- Board.update
public void update(String title, String content, Boolean isPublic) {
this.title = title;
this.content = content;
this.isPublic = isPublic;
}
결과
- 제목, 내용, 공개 글 작성 체크박스 여부를 잘 반영함
- 체크박스 해제 시 개인 글 설정



6. 글 페이징
- Main 화면에 페이지 정보를 추가해야 함 : MainDTO 설계
@Data
public static class MainDTO {
private Integer prev;
private Integer next;
private Integer current;
private Integer totalCount;
private Integer totalPage;
private Integer size;
private Boolean isFirst; // currentPage == 0
private Boolean isLast; // totalCount, totalPage == currentPage
private Integer pageSize;
private Integer pageIndex;
private List<Board> boards;
private List<Integer> numbers; // 20개 [1, 2, 3, 4, 5, 6, 7] -> {{#model.numbers}} -> {{.}}
public MainDTO(List<Board> boards, Integer current, Integer totalCount) {
this.boards = boards;
this.prev = current - 1;
this.next = current + 1;
this.size = 3;
this.totalCount = totalCount; // given
this.totalPage = makeTotalPage(totalCount, size);
this.isFirst = current == 0;
this.isLast = current == totalPage - 1;
this.pageSize = 5;
// 2-1. makeNumbers
this.numbers = makeNumbers(current, pageSize, totalPage);
this.pageIndex = 0;
// 1. 페이지 전체 넣기
/*
for (int i = 0; i < totalCount; i++) {
this.numbers.add(i);
}
*/
// 2. 페이지 묶음
for (int i = 0; i < pageSize; i++) {
pageIndex = current / pageSize; // 현재 페이지 index : 페이지 목차
if (i % pageSize == 0) numbers.clear(); // 페이지를 불러올 때마다 페이지 리스트 클리어
int page = pageIndex * pageSize + i; // 페이지 변수 : 페이지 index * pageSize + i
// 0 ~ 4 : 0 * 5 + 0~4, 5 ~ 9 : 1 * 5 + 0~4, ...
if (page < totalPage) numbers.add(page); // 최대 페이지 전까지 추가
}
}
// 2-1. makeNumbers
private List<Integer> makeNumbers(int current, int pageSize, int totalPage) {
List<Integer> numbers = new ArrayList<>();
int start = (current / pageSize) * pageSize;
int end = Math.min(start + pageSize, totalPage);
for (int i = start; i < end; i++) {
numbers.add(i);
}
return numbers;
}
private Integer makeTotalPage(int totalCount, int size) {
return totalCount / size + (totalCount % size == 0 ? 0 : 1);
}
}
글 페이징
- 현재 페이지 정보를 불러오기 위해, 글 목록 보기를 일부 개수만큼 잘라서 들고 와야 함
- 3 개씩 들고 옴 : size에 저장
- 페이징 사이즈 갯수는 여러 곳에 재사용할 수 있도록 추후 final static 선언이 필요
- 이때, board 객체에는 3개의 글밖에 담기지 않으므로, 쿼리를 통한 총 board 개수가 필요
- 새 쿼리 생성 필요
- 페이지의 목차가 필요
- 0 ~ 4 / 5 ~ 9 / 10 ~ 14 / … → pageIndex * pageSize + 0 ~ 4
- 페이지를 담을 list 설계 : List<Integer> numbers
- 페이지 index = 현재 페이지 / 페이지 목록 크기
- 페이지는 1번만 초기화해야 함
- 나머지가 0이면 초기화 : i가 0일 때 (맨 처음에만) 초기화
- 페이지 계산 : 모듈화 필요
- 0 ~ 4 : 페이지 index=0이므로 0 * 5= 0 → 0 + (0 ~ 4)
- 5 ~ 9 : 페이지 index=1이므로 1 * 5 = 5 → 5 + (0 ~ 4)
- 10 ~ 14 : 페이지 index=2이므로 2 * 5 = 10 → 10 + (0 ~ 4)
- 페이지가 총 페이지 수보다 작을 때만 추가
- BoardController
- requestParameter로 현재 페이지를 받아와야 함
@GetMapping("/")
public String list(HttpServletRequest request, @RequestParam(required = false, value = "page", defaultValue = "0") Integer page) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
request.setAttribute("model", boardService.글목록보기(null, page));
} else {
request.setAttribute("model", boardService.글목록보기(sessionUser.getId(), page));
}
return "board/list";
}
- BoardService
- MainDTO 반환 : 페이지 정보가 추가됨
public BoardResponse.MainDTO 글목록보기(Integer userId, Integer page) {
if (userId == null) {
return new BoardResponse.MainDTO(boardRepository.findAll(page), page, boardRepository.totalCount().intValue());
} else {
return new BoardResponse.MainDTO(boardRepository.findAll(userId, page), page, boardRepository.totalCount(userId).intValue());
}
}
- BoardRepository
- findAll
- setFirstResult(n) → 데이터 인덱스 (어디서부터 찾을건지 확인)
- setMaxResults(n) → 최대 표시 개수 (board 출력 개수)
- totalCount
- count(*)로 총 board 갯수 반환
// localhost:8080?page=0
public List<Board> findAll(int page) {
String sql = "select b from Board b where b.isPublic = true order by b.id desc";
Query query = em.createQuery(sql, Board.class);
query.setFirstResult(page * 3);
query.setMaxResults(3);
return query.getResultList();
}
public List<Board> findAll(Integer userId, int page) {
String sql = "select b from Board b where b.isPublic = true or b.user.id = :userId order by b.id desc";
Query query = em.createQuery(sql, Board.class);
query.setParameter("userId", userId);
query.setFirstResult(page * 3);
query.setMaxResults(3);
return query.getResultList();
}
// 그룹 함수 : Long 리턴
// 1. 로그인 안 했을 때 : 4개
public Long totalCount() {
Query q = em.createQuery("select count(b) from Board b where b.isPublic = true", Long.class);
return (Long) q.getSingleResult();
}
// 2-1. ssar로 로그인 했을 때 : 5개
// 2-2. cos로 로그인 했을 때 : 4개
public Long totalCount(int userId) {
Query q = em.createQuery("select count(b) from Board b where b.isPublic = true or b.user.id = :userId", Long.class);
q.setParameter("userId", userId);
return (Long) q.getSingleResult();
}
- list.mustache
<ul class="pagination d-flex justify-content-center">
<li class="page-item {{#model.isFirst}} disabled {{/model.isFirst}}"><a class="page-link"
href="?page={{model.prev}}
">Previous</a></li>
{{#model.numbers}}
<li class="page-item"><a class="page-link" href="?page={{.}}">{{.}}</a></li>
{{/model.numbers}}
<li class="page-item {{#model.isLast}} disabled {{/model.isLast}}"><a class="page-link"
href="?page={{model.next}}">Next</a></li>
</ul>
결과
- 현재 페이지(default)가 0이므로 Previous는 비활성화, 0 ~ 4까지 페이지 번호 출력

- 5페이지가 넘어가면, 페이지 일부만 출력 (글이 20개이므로, 5 ~ 6까지만 출력)

7. 글 검색
- BoardController
@GetMapping("/")
public String list(HttpServletRequest request, @RequestParam(required = false, value = "page", defaultValue = "0") Integer page,
@RequestParam(required = false, value = "keyword", defaultValue = "") String keyword) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
request.setAttribute("model", boardService.글목록보기(null, page, keyword));
} else {
request.setAttribute("model", boardService.글목록보기(sessionUser.getId(), page, keyword));
}
System.out.println("keyword : " + keyword);
return "board/list";
}
- BoardResponse
@Data
public static class MainDTO {
private Integer prev;
private Integer next;
private Integer current;
private Integer totalCount;
private Integer totalPage;
private Integer size;
private Boolean isFirst; // currentPage == 0
private Boolean isLast; // totalCount, totalPage == currentPage
private Integer pageSize;
private String keyword;
private List<Board> boards;
private List<Integer> numbers; // 20개 [1, 2, 3, 4, 5, 6, 7] -> {{#model.numbers}} -> {{.}}
public MainDTO(List<Board> boards, Integer current, Integer totalCount, String keyword) {
this.boards = boards;
this.prev = current - 1;
this.next = current + 1;
this.size = 3;
this.totalCount = totalCount; // given
this.totalPage = makeTotalPage(totalCount, size);
this.isFirst = current == 0;
this.isLast = current == totalPage - 1;
this.pageSize = 5;
this.numbers = makeNumbers(current, pageSize, totalPage);
// keyword 값이 있거나 null
this.keyword = keyword;
// 1. 페이지 전체 넣기
/*
for (int i = 0; i < totalCount; i++) {
this.numbers.add(i);
}
*/
// 2. 페이지 묶음
/*for (int i = 0; i < pageSize; i++) {
pageIndex = current / pageSize; // 현재 페이지 index : 페이지 목차
if (i % pageSize == 0) numbers.clear(); // 페이지를 불러올 때마다 페이지 리스트 클리어
int page = pageIndex * pageSize + i; // 페이지 변수 : 페이지 index * pageSize + i
// 0 ~ 4 : 0 * 5 + 0~4, 5 ~ 9 : 1 * 5 + 0~4, ...
if (page < totalPage) numbers.add(page); // 최대 페이지 전까지 추가
}*/
}
- BoardService
public BoardResponse.MainDTO 글목록보기(Integer userId, Integer page, String keyword) {
if (userId == null) {
return new BoardResponse.MainDTO(boardRepository.findAll(page, keyword), page, boardRepository.totalCount(keyword).intValue(), keyword);
} else {
return new BoardResponse.MainDTO(boardRepository.findAll(userId, page, keyword), page, boardRepository.totalCount(userId, keyword).intValue(), keyword);
}
}
- BoardRepository
// localhost:8080?page=0
public List<Board> findAll(int page, String keyword) {
String sql = "";
if (!(keyword.isBlank()))
sql += "select b from Board b where b.isPublic = true and b.title like :keyword order by b.id desc";
else sql += "select b from Board b where b.isPublic = true order by b.id desc";
Query query = em.createQuery(sql, Board.class);
// keyword를 포함 : title like %keyword%
if (!(keyword.isBlank())) query.setParameter("keyword", "%" + keyword + "%");
query.setFirstResult(page * 3);
query.setMaxResults(3);
return query.getResultList();
}
public List<Board> findAll(Integer userId, int page, String keyword) {
String sql = "";
if (!(keyword.isBlank()))
sql += "select b from Board b where b.isPublic = true or b.user.id = :userId and b.title like :keyword order by b.id desc";
else sql += "select b from Board b where b.isPublic = true or b.user.id = :userId order by b.id desc";
Query query = em.createQuery(sql, Board.class);
// keyword를 포함 : title like %keyword%
if (!(keyword.isBlank())) query.setParameter("keyword", "%" + keyword + "%");
query.setParameter("userId", userId);
query.setFirstResult(page * 3);
query.setMaxResults(3);
return query.getResultList();
}
// 그룹 함수 : Long 리턴
// 1. 로그인 안 했을 때 : 4개
public Long totalCount(String keyword) {
String sql = "";
if (!(keyword.isBlank()))
sql += "select count(b) from Board b where b.isPublic = true and b.title like :keyword";
else sql += "select count(b) from Board b where b.isPublic = true";
Query query = em.createQuery(sql, Long.class);
// keyword를 포함 : title like %keyword%
if (!(keyword.isBlank())) query.setParameter("keyword", "%" + keyword + "%");
return (Long) query.getSingleResult();
}
// 2-1. ssar로 로그인 했을 때 : 5개
// 2-2. cos로 로그인 했을 때 : 4개
public Long totalCount(int userId, String keyword) {
String sql = "";
if (!(keyword.isBlank()))
sql += "select count(b) from Board b where b.isPublic = true or b.user.id = :userId and b.title like :keyword";
else sql += "select count(b) from Board b where b.isPublic = true or b.user.id = :userId";
Query query = em.createQuery(sql, Long.class);
// keyword를 포함 : title like %keyword%
if (!(keyword.isBlank())) query.setParameter("keyword", "%" + keyword + "%");
query.setParameter("userId", userId);
return (Long) query.getSingleResult();
}
결과
- keyword 검색 시 keyword가 제목에 포함된 글을 찾아 출력, 페이징도 동작 확인

Share article