package shop.mtcoding.blog.board;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor@Controller
public class BoardController {
private final BoardService boardService;
private final HttpSession session;
@GetMapping("/")
public String list(HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) {
request.setAttribute("models", boardService.글목록보기(null));
} else {
request.setAttribute("models", boardService.글목록보기(sessionUser.getId()));
}
return "board/list";
}
@PostMapping("/board/save")
public String save(BoardRequest.SaveDTO saveDTO) {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) throw new RuntimeException("인증이 필요합니다.");
boardService.글쓰기(saveDTO, sessionUser);
return "redirect:/";
}
@GetMapping("/board/save-form")
public String saveForm() {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) throw new RuntimeException("인증이 필요합니다.");
return "board/save-form";
}
@GetMapping("/board/{id}")
public String detail(@PathVariable("id") Integer id, HttpServletRequest request) {
Board board = boardService.글상세보기(id);
request.setAttribute("model", board);
return "board/detail";
}
}
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor@Service
public class BoardService {
private final BoardRepository boardRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public Board 글상세보기(Integer id) {
return boardRepository.findByIdJoinUser(id);
}
}
BoardRepository
Join Query 필요
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
BoardResponse
package shop.mtcoding.blog.board;
import lombok.Data;
import java.sql.Timestamp;
public class BoardResponse {
// 깊은 복사
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isPublic;
private Boolean isOwner; // 대문자는 값을 안 넣으면 null, 소문자는 false
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 = sessionUserId == board.getUser().getId(); // 같으면 true 같지 않으면 false
this.userId = board.getUser().getId();
this.username = board.getUser().getUsername();
this.createdAt = board.getCreatedAt();
}
}
}
package shop.mtcoding.blog.board;
import lombok.Data;
import java.sql.Timestamp;
public class BoardResponse {
// 깊은 복사
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isPublic;
private Boolean isOwner; // 대문자는 값을 안 넣으면 null, 소문자는 false
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 = sessionUserId == board.getUser().getId(); // 같으면 true 같지 않으면 false
this.username = board.getUser().getUsername();
this.createdAt = board.getCreatedAt();
}
}
}
BoardController
package shop.mtcoding.blog.board;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor@Controller
public class BoardController {
private final BoardService boardService;
private final HttpSession session;
@GetMapping("/")
public String list(HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) {
request.setAttribute("models", boardService.글목록보기(null));
} else {
request.setAttribute("models", boardService.글목록보기(sessionUser.getId()));
}
return "board/list";
}
@PostMapping("/board/save")
public String save(BoardRequest.SaveDTO saveDTO) {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) throw new RuntimeException("인증이 필요합니다.");
boardService.글쓰기(saveDTO, sessionUser);
return "redirect:/";
}
@GetMapping("/board/save-form")
public String saveForm() {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) throw new RuntimeException("인증이 필요합니다.");
return "board/save-form";
}
@GetMapping("/board/{id}")
public String detail(@PathVariable("id") Integer id, HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
// 비로그인 시 상세보기
Integer sessionUserId = (sessionUser ==null ? null : sessionUser.getId());
BoardResponse.DetailDTO detailDTO = boardService.글상세보기(id, sessionUserId);
request.setAttribute("model", detailDTO);
return "board/detail";
}
}
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor@Service
public class BoardService {
private final BoardRepository boardRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
Board board = boardRepository.findByIdJoinUser(id);
BoardResponse.DetailDTO detailDTO =new BoardResponse.DetailDTO(board, userId);
return detailDTO;
}
}
BoardRepository
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
<div class="my-3 d-flex align-items-center"><i id="likeIcon" class="fa fa-heart" style="font-size:20px; color:{{#model.isLove}}red{{/model.isLove}}{{^model.isLove}}black{{/model.isLove}} " onclick="likeToggle()"></i><span class="ms-1"><b id="likeCount">{{model.loveCount}}</b>명이 이 글을 좋아합니다</span></div>
LoveRepository
package shop.mtcoding.blog.love;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@RequiredArgsConstructor@Repository
public class LoveRepository {
private final EntityManager em;
public Love findByUserIdAndBoardId(int userId, int boardId) {
Query query = em.createQuery("select lo from Love lo where lo.user.id = :userId and lo.board.id = :boardId", Love.class);
query.setParameter("userId", userId);
query.setParameter("boardId", boardId);
try {
return (Love) query.getSingleResult(); //unique 제약조건이기 때문에 SingleResult
} catch (Exception e) {
returnnull;
}
}
}
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog.love.Love;
import shop.mtcoding.blog.love.LoveRepository;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor@Service
public class BoardService {
private final BoardRepository boardRepository;
private final LoveRepository loveRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
Board board = boardRepository.findByIdJoinUser(id);
Love love = loveRepository.findByUserIdAndBoardId(userId, id);
Boolean isLove = love ==null ? false : true;
BoardResponse.DetailDTO detailDTO =new BoardResponse.DetailDTO(board, userId, isLove);
return detailDTO;
}
}
ssar 로그인
cos 로그인
loveCount 표시
BoardResponse
package shop.mtcoding.blog.board;
import lombok.Data;
import shop.mtcoding.blog.reply.Reply;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
publicclassBoardResponse {
@DatapublicstaticclassDTO {
private List<Board> boards;
private Integer prev;
private Integer next;
private Integer current;
private Integer size; // 3private Integer totalCount;
private Integer totalPage;
privateboolean isFirst; // current == 0privateboolean isLast; // 다음페이지에서 못 넘어가게 계산 필요 -> totalCount, size = 3 totalPages == currentprivate List<Integer> numbers;
publicDTO(List<Board> boards, Integer current, Integer totalCount) {
this.boards = boards;
this.prev = current - 1;
this.next = current + 1;
this.size = 3; // 보통은 final로 따로 빼서 씀 - 그래야 수정이 적어진다this.totalCount = totalCount; // given 으로 처리 후 따로 연산(given으로 test 먼저 필요) -> test 끝나면 DB에서 들고옴this.totalPage = makeTotalPage(totalCount, size);
this.isFirst = current == 0;
this.isLast = (totalPage - 1) == current; // totalPages는 1부터 시작하는데 current는 0부터 시작하니까 totalPages-1 필요
System.out.println("isLast: " + isLast);
this.numbers = makeNumbers(current, totalPage);
}
// page 계산 함수private Integer makeTotalPage(int totalCount, int size) {
intrest= totalCount % size > 0 ? 1 : 0; // 나머지 -> 5 / 3 = 나머지 2 , 6 / 3 = 나머지 0 // 나머지가 0이 아니면 rest = 1을 page에 더함return totalCount / size + rest; // 전체 페이지
}
private List<Integer> makeNumbers(int current, int totalPage) {
List<Integer> numbers = newArrayList<>();
intstart= (current / 5) * 5;
intend= Math.min(start + 5, totalPage);
for (inti= start; i < end; i++) {
numbers.add(i);
}
return numbers;
}
}
// 깊은 복사@DatapublicstaticclassDetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isPublic;
private Boolean isOwner; // 대문자는 값을 안 넣으면 null, 소문자는 falseprivate Boolean isLove;
private Integer loveCount;
private String username;
private Timestamp createdAt;
private Integer loveId;
private List<ReplyDTO> replies;
@Data// DetailDTO안에 있기 때문에 외부 클래스가 아닌 내부클래스publicclassReplyDTO {
private Integer id;
private String content;
private String username;
private Boolean isOwner;
publicReplyDTO(Reply reply, Integer sessionUserId) {
this.id = reply.getId();
this.content = reply.getContent();
this.username = reply.getUser().getUsername();
this.isOwner = reply.getUser().getId().equals(sessionUserId);
}
}
// 템플릿엔진이 조건문 비교를 허용해주지 않기 때문에 필요함publicDetailDTO(Board board, Integer sessionUserId, Boolean isLove, Integer loveCount, Integer loveId) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.isPublic = board.getIsPublic();
this.isOwner = sessionUserId == board.getUser().getId(); // 같으면 true 같지 않으면 falsethis.username = board.getUser().getUsername();
this.createdAt = board.getCreatedAt();
this.isLove = isLove;
this.loveCount = loveCount;
this.loveId = loveId;
List<ReplyDTO> repliesDTO = newArrayList<>();
for (Reply reply : board.getReplies()) {
ReplyDTOreplyDTO=newReplyDTO(reply, sessionUserId);
repliesDTO.add(replyDTO);
}
this.replies = repliesDTO;
}
}
}
LoveRepository
package shop.mtcoding.blog.love;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@RequiredArgsConstructor@Repository
public class LoveRepository {
private final EntityManager em;
public Love findByUserIdAndBoardId(int userId, int boardId) {
Query query = em.createQuery("select lo from Love lo where lo.user.id = :userId and lo.board.id = :boardId", Love.class);
query.setParameter("userId", userId);
query.setParameter("boardId", boardId);
try {
return (Love) query.getSingleResult(); //unique 제약조건이기 때문에 SingleResult
} catch (Exception e) {
returnnull;
}
}
public Long findByLoveCount(int boardId) {
Query query = em.createQuery("select count(lo) from Love lo where lo.board.id = :boardId");
query.setParameter("boardId", boardId);
Long count = (Long) query.getSingleResult();
try {
return count; //unique 제약조건이기 때문에 SingleResult
} catch (Exception e) {
returnnull;
}
}
}
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog.love.Love;
import shop.mtcoding.blog.love.LoveRepository;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor@Service
public class BoardService {
private final BoardRepository boardRepository;
private final LoveRepository loveRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
Board board = boardRepository.findByIdJoinUser(id);
Love love = loveRepository.findByUserIdAndBoardId(userId, id);
Boolean isLove = love ==null ? false : true;
Long loveCount = loveRepository.findByLoveCount(board.getId());
BoardResponse.DetailDTO detailDTO =new BoardResponse.DetailDTO(board, userId, isLove, loveCount.intValue());
return detailDTO;
}
}
➡ 게시글 5번 → 좋아요 1명
➡ 게시글 4번 → 좋아요 2명
➡ 게시글 3~1번 → 좋아요 0명
한방 Query version
SELECT
b.id,
b.title,
b.content,
b.is_public,
CASEWHEN b.user_id =4THENtrueELSEfalseENDAS is_owner,
u.username,
b.created_at,
(
SELECTCOUNT(l.id)
FROM love_tb l
WHERE l.board_id =4
) AS love_count,
(
SELECTCASEWHENCOUNT(l2.id) >0THENtrueELSEfalseENDFROM love_tb l2
WHERE l2.board_id =4AND l2.user_id =1
) AS is_loved
FROM board_tb b
JOIN user_tb u ON b.user_id = u.id
WHERE b.id =4;
BoardRepository
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
// 한방쿼리 (h2 query, om -> dto)
public BoardResponse.DetailDTO findDetail(Integer boardId, Integer userId) {
String sql= """
SELECT new shop.mtcoding.blog.board.BoardResponse$DetailDTO(
b.id,
b.title,
b.content,
b.isPublic,
CASE WHEN b.user.id = :userId THEN true ELSE false END isOwner,
b.user.username,
b.createdAt,
(SELECT COUNT(l.id) FROM Love l WHERE l.board.id = :boardId),
(SELECT CASE WHEN COUNT(l2) > 0 THEN true ELSE false END
FROM Love l2
WHERE l2.board.id = :boardId AND l2.user.id = :userId)
)
FROM Board b
WHERE b.id = :boardId
""";
Query query = em.createQuery(sql);
query.setParameter("boardId", boardId);
query.setParameter("userId", userId);
return (BoardResponse.DetailDTO) query.getSingleResult();
}
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 /fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog.love.LoveRepository;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor@Service
public class BoardService {
private final BoardRepository boardRepository;
private final LoveRepository loveRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
BoardResponse.DetailDTO detailDTO = boardRepository.findDetail(id, userId);
return detailDTO;
}
}
좋아요 & 좋아요 취소
JavaScript로 화면에 있는 data 사용하는 방법
함수의 매개변수에 인수로 전달한다
input type ‘ hidden’ 사용
→ 통신할 때 사용하기 위해서 hidden으로 id 심어놓기
→ 바깥에 심어서 JavaScript로 땡겨옴
dataset 사용
LoveRequest
package shop.mtcoding.blog.love;
import lombok.Data;
import shop.mtcoding.blog.board.Board;
import shop.mtcoding.blog.user.User;
public class LoveRequest {
@Data
public static class SaveDTO {
private Integer boardId;
public Love toEntity(Integer sessionUserId) {
return Love.builder()
.board(Board.builder().id(boardId).build())
.user(User.builder().id(sessionUserId).build())
.build();
}
}
}
LoveResponse
package shop.mtcoding.blog.love;
import lombok.Data;
public class LoveResponse {
@Data
public static class SaveDTO {
private Integer loveId;
private Integer loveCount;
public SaveDTO(Integer loveId, Integer loveCount) {
this.loveId = loveId;
this.loveCount = loveCount;
}
}
@Data
public static class DeleteDTO {
private Integer loveCount;
public DeleteDTO(Integer loveCount) {
this.loveCount = loveCount;
}
}
}
LoveController
package shop.mtcoding.blog.love;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.Resp;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor@RestController// 파일이 아닌 데이터 리턴하는 컨트롤러 (Ajax)
public class LoveController {
private final LoveService loveService;
private final HttpSession session;
@PostMapping("/love")
public Resp<?> saveLove(@RequestBody LoveRequest.SaveDTO reqDTO) {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) throw new RuntimeException("인증이 필요합니다.");
LoveResponse.SaveDTO respDTO = loveService.좋아요(reqDTO, sessionUser.getId());
return Resp.ok(respDTO);
}
@DeleteMapping("/love/{id}")
public Resp<?> deleteLove(@PathVariable("id") Integer id) {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser ==null) throw new RuntimeException("인증이 필요합니다.");
LoveResponse.DeleteDTO respDTO = loveService.좋아요취소(id); // loveId
return Resp.ok(respDTO);
}
}
LoveService
package shop.mtcoding.blog.love;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog.board.BoardRepository;
import shop.mtcoding.blog.user.UserRepository;
@RequiredArgsConstructor@Service
public class LoveService {
private final LoveRepository loveRepository;
private final BoardRepository boardRepository;
private final UserRepository userRepository;
@Transactional
public LoveResponse.SaveDTO 좋아요(LoveRequest.SaveDTO reqDTO, Integer sessionUserId) {
Love lovePs = loveRepository.save(reqDTO.toEntity(sessionUserId));
Long loveCount = loveRepository.findByBoardId(reqDTO.getBoardId());
returnnew LoveResponse.SaveDTO(lovePs.getId(), loveCount.intValue());
}
@Transactional
public LoveResponse.DeleteDTO 좋아요취소(Integer id) {
Love lovePs = loveRepository.findById(id);
if (lovePs ==null) throw new RuntimeException("취소할 좋아요가 없습니다.");
Integer boardId = lovePs.getBoard().getId();
loveRepository.deleteById(id);
Long loveCount = loveRepository.findByBoardId(boardId);
returnnew LoveResponse.DeleteDTO(loveCount.intValue());
}
}
LoveRepository
package shop.mtcoding.blog.love;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@RequiredArgsConstructor@Repository
public class LoveRepository {
private final EntityManager em;
public Love findByUserIdAndBoardId(Integer userId, Integer boardId) {
Query query = em.createQuery("select lo from Love lo where lo.user.id = :userId and lo.board.id = :boardId", Love.class);
query.setParameter("userId", userId);
query.setParameter("boardId", boardId);
try {
return (Love) query.getSingleResult(); //unique 제약조건이기 때문에 SingleResult
} catch (Exception e) {
returnnull;
}
}
public Long findByBoardId(int boardId) {
Query query = em.createQuery("select count(lo) from Love lo where lo.board.id = :boardId");
query.setParameter("boardId", boardId);
Long count = (Long) query.getSingleResult();
try {
return count; //unique 제약조건이기 때문에 SingleResult
} catch (Exception e) {
returnnull;
}
}
public Love save(Love love) {
em.persist(love);
return love;
}
public void deleteById(Integer id) {
em.createQuery("delete from Love lo where lo.id = :id")
.setParameter("id", id)
.executeUpdate();
}
public Love findById(Integer id) {
return em.find(Love.class, id);
}
}
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 /fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
public Board findByIdJoinUserAndReplies(Integer id) {
Query query = em.createQuery("select b from Board b join fetch b.user u left join fetch b.replies where b.id = :id", Board.class); //leftjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
BoardRepositoryTest
package shop.mtcoding.blog.board;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import java.util.List;
@Import(BoardRepository.class) // BoardRepository
@DataJpaTest// EntityManager, PC
public class BoardRepositoryTest {
@Autowired// DI
private BoardRepository boardRepository;
@Test
public void findById_test() {
// given
Integer boardId =4;
Board board = boardRepository.findById(boardId);
}
}
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 /fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
public Board findByIdJoinUserAndReplies(Integer id) {
Query query = em.createQuery("select b from Board b join fetch b.user u left join fetch b.replies r where b.id = :id", Board.class); //leftjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
BoardRepositoryTest
package shop.mtcoding.blog.board;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import shop.mtcoding.blog.reply.Reply;
import java.util.List;
@Import(BoardRepository.class) // BoardRepository
@DataJpaTest// EntityManager, PC
public class BoardRepositoryTest {
@Autowired// DI
private BoardRepository boardRepository;
@Test
public void findByIdJoinUserAndReplies_test() {
// given
Integer boardId =4;
Board board = boardRepository.findByIdJoinUserAndReplies(boardId);
for (Reply reply : board.getReplies()) {
System.out.println("-----------------------------------Lazy Loading Select");
System.out.println(reply.getUser().getUsername());
System.out.println("------------------------");
}
}
}
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 /fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
public Board findByIdJoinUserAndReplies(Integer id) {
Query query = em.createQuery("select b from Board b join fetch b.user u left join fetch b.replies r join fetch r.user where b.id = :id", Board.class); //leftjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
BoardRepositoryTest
package shop.mtcoding.blog.board;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import shop.mtcoding.blog.reply.Reply;
import java.util.List;
@Import(BoardRepository.class) // BoardRepository
@DataJpaTest// EntityManager, PC
public class BoardRepositoryTest {
@Autowired// DI
private BoardRepository boardRepository;
@Test
public void findByIdJoinUserAndReplies_test() {
// given
Integer boardId =4;
Board board = boardRepository.findByIdJoinUserAndReplies(boardId);
for (Reply reply : board.getReplies()) {
System.out.println("-----------------------------------Lazy Loading Select");
System.out.println(reply.getUser().getUsername());
System.out.println("------------------------");
}
}
}
캐싱되서 조회없이 값을 바로 가져온다
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog.love.Love;
import shop.mtcoding.blog.love.LoveRepository;
import shop.mtcoding.blog.reply.ReplyRepository;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor@Service
public class BoardService {
private final BoardRepository boardRepository;
private final LoveRepository loveRepository;
private final ReplyRepository replyRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
Board board = boardRepository.findByIdJoinUserAndReplies(id);
Love love = loveRepository.findByUserIdAndBoardId(userId, id);
Boolean isLove = love ==null ? false : true;
Integer loveId = love ==null ? null : love.getId();
Long loveCount = loveRepository.findByBoardId(board.getId());
BoardResponse.DetailDTO detailDTO =new BoardResponse.DetailDTO(board, userId, isLove, loveCount.intValue(), loveId);
return detailDTO;
}
}
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
BoardRepositoryTest
package shop.mtcoding.blog.board;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import java.util.List;
@Import(BoardRepository.class) // BoardRepository
@DataJpaTest// EntityManager, PC
public class BoardRepositoryTest {
@Autowired// DI
private BoardRepository boardRepository;
@Test
public void findAll_test() {
// given
Integer userId =1;
//when
List<Board> boardList = boardRepository.findAll(userId);
// Lazy -> Board ->User(id=1)
// Eager -> N+1-> Board조회 -> 연관된 User 유저 수 만큼 주회
// Eager ->Join-> 한방쿼리
// System.out.println("--------------------");
// boardList.get(0).getUser().getUsername();
// System.out.println("--------------------");
// eye
for (Board board : boardList) {
System.out.print(board.getId() + "," + board.getTitle() + "," + board.getContent() + "," + board.getIsPublic() + "," + board.getUser().getId() + "," + board.getCreatedAt());
System.out.println();
}
}
@Test
public void findByIdJoinUser_test() {
// given
Integer id =1;
//when
boardRepository.findByIdJoinUser(id);
// eye
}
}
⬇ board안에 있는 user 객체도 같이 프로잭션됨
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=?
BoardRepository
fetch를 안 쓸때
package shop.mtcoding.blog.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor@Repository
public class BoardRepository {
private final EntityManager em;
public void save(Board board) {
em.persist(board);
}
public List<Board> findAll(Integer userId) { //Integer를 써야 null을 넘길 수 있다
// 동적 query
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 query =null;
if (userId ==null) {
query = em.createQuery(s2, Board.class);
} else {
query = em.createQuery(s1, Board.class);
query.setParameter("userId", userId);
}
return query.getResultList();
}
public Board findById(Integer id) {
return em.find(Board.class, id);
}
public Board findByIdJoinUser(Integer id) {
// b -> board에 있는 필드만 프로잭션 fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨
Query query = em.createQuery("select b from Board b join b.user u where b.id = :id", Board.class); //innerjoin (on절은 생략가능하다) -> 객체지향 쿼리
query.setParameter("id", id);
return (Board) query.getSingleResult();
}
}
BoardRepositoryTest
package shop.mtcoding.blog.board;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import java.util.List;
@Import(BoardRepository.class) // BoardRepository
@DataJpaTest// EntityManager, PC
public class BoardRepositoryTest {
@Autowired// DI
private BoardRepository boardRepository;
@Test
public void findAll_test() {
// given
Integer userId =1;
//when
List<Board> boardList = boardRepository.findAll(userId);
// Lazy -> Board ->User(id=1)
// Eager -> N+1-> Board조회 -> 연관된 User 유저 수 만큼 주회
// Eager ->Join-> 한방쿼리
// System.out.println("--------------------");
// boardList.get(0).getUser().getUsername();
// System.out.println("--------------------");
// eye
for (Board board : boardList) {
System.out.print(board.getId() + "," + board.getTitle() + "," + board.getContent() + "," + board.getIsPublic() + "," + board.getUser().getId() + "," + board.getCreatedAt());
System.out.println();
}
}
@Test
public void findByIdJoinUser_test() {
// given
Integer id =1;
//when
boardRepository.findByIdJoinUser(id);
// eye
}
}
⬇ board만 프로잭션
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=?