Reply 엔티티와 Board 엔티티는 n : 1의 관계를 가지고 있습니다.
Reply 엔티티
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "reply_tb")
@Entity
public class Reply {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String comment;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
@CreationTimestamp
private Timestamp createdAt;
@Builder
public Reply(Integer id, String comment, User user, Board board, Timestamp createdAt) {
this.id = id;
this.comment = comment;
this.user = user;
this.board = board;
this.createdAt = createdAt;
}
}
Board 엔티티
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "board_tb")
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<Reply> replies;
@CreationTimestamp
private Timestamp createdAt;
@Builder
public Board(Integer id, String title, String content, User user, Timestamp createdAt) {
this.id = id;
this.title = title;
this.content = content;
this.user = user;
this.createdAt = createdAt;
}
}
BoardResponseDTO
public class BoardResponse {
@Data
public static class DetailDTO {
private int id;
private String title;
private String content;
private String createdAt;
private Integer userId;
private String username;
private boolean isOwner = false;
private List<ReplyDTO> replies;
@Data
class ReplyDTO {
private int id;
private String comment;
private int userId;
private String username;
private boolean isOwner = false; // 이너클래스라 변수 이름이 같아도 된다.
public ReplyDTO(Reply reply, User sessionUser) {
this.id = reply.getId();
this.comment = reply.getComment();
this.userId = reply.getUser().getId();
this.username = reply.getUser().getUsername();
if (sessionUser != null) {
this.isOwner = sessionUser.getId().equals(reply.getUser().getId());
}
}
}
public DetailDTO(Board board, User sessionUser) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.createdAt = MyDate.formatToStr(board.getCreatedAt());
this.userId = board.getUser().getId();
this.username = board.getUser().getUsername();
if (sessionUser != null) {
this.isOwner = sessionUser.getUsername().equals(board.getUser().getUsername());
}
this.replies = board.getReplies().stream().map(r -> new ReplyDTO(r, sessionUser)).toList();
}
}
}
ReplyDTO
if (sessionUser != null) {
this.isOwner = sessionUser.getId().equals(reply.getUser().getId());
}
- 세션에 저장된 User 정보와 Reply(댓글)를 작성한 User 정보를 비교합니다.
- Integer 비교는 ‘==’ 이 아니라 .equals로 해야 합니다.
- int와 달리 Integer는 객체의 참조를 비교합니다.
- 값이 아닌 메모리 주소를 비교하므로, 동일한 값을 가진 다른 객체는 false를 반환할 수 있습니다.
BoardRepository
@RequiredArgsConstructor
@Repository
public class BoardRepository {
private final EntityManager em;
public void delete(int id) {
em.createQuery("delete from Board b where id = :id")
.setParameter("id", id)
.executeUpdate();
}
}
BoardService
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardRepository boardRepository;
@Transactional
public void 게시글삭제(int id, User sessionUser) {
Board boardPS = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("게시글 아이디를 찾을 수 없습니다."));
if (!sessionUser.getId().equals(boardPS.getUser().getId())) {
throw new Exception403("권한이 없습니다.");
}
boardRepository.delete(id);
}
}
BoardController
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService boardService;
private final HttpSession session;
// 메인화면 이동
@GetMapping("/")
public String list(Model model) {
List<BoardResponse.DTO> boardList = boardService.게시글목록보기();
model.addAttribute("models", boardList);
return "index";
}
// 글 삭제
@PostMapping("/board/{id}/delete")
public String delete(@PathVariable("id") int id) {
User sessionUser = (User) session.getAttribute("sessionUser");
boardService.게시글삭제(id, sessionUser);
return "redirect:/";
}
}
이렇게 작성할 경우 외래키 제약 조건 때문에 게시글 삭제가 되지 않습니다.
삭제 구현 방법
1. FK에 @OnDelete 설정 걸기
Reply.java
@OnDelete(action = OnDeleteAction.CASCADE)
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
- @OnDelete(action = OnDeleteAction.CASCADE)
- 부모 엔티티(Board)가 삭제될 때 종속된 자식 엔티티(Reply)도 함께 삭제되도록 설정합니다.
2. Reply 삭제 메서드 추가
ReplyRepository.java
public void deleteAll(int boardId){
Query q = em.createQuery("delete from Reply r where r.board.id=:boardId");
q.setParameter("boardId", boardId);
q.executeUpdate();
}
3. Reply의 외래키를 null로 설정
ReplyRepository.java
public void updateNull(int boardId){
Query q = em.createQuery("update Reply r set r.board.id=null where r.board.id=:boardId");
q.setParameter("boardId", boardId);
q.executeUpdate();
}
4. 외래키를 설정하지만 제약 조건은 비활성화
Reply.java
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) // 외래 키 제약 조건 비활성화
private Board board;
5. Board를 논리 삭제
- Board에 visible 컬럼을 추가하고 삭제 시 false로 update합니다.
Share article