최원종의 개발 블로그

V2(PersistentContext)-패키지 구조 변경 본문

Spring boot 입문

V2(PersistentContext)-패키지 구조 변경

chl6698 2026. 5. 7. 09:52

기존 패키지 구조

V1: 계층 중심 패키지 구조 
com.tenco.blog
├── controller/               # 계층별 분리
│   ├── BoardController
│   └── UserController
├── model/                    # 모든 엔티티가 한 곳에
│   ├── Board
│   └── User  
├── service/                 # 모든 서비스 클래스가 한 곳에
│   ├── BoardService
├── repository/               # 모든 리포지토리가 한 곳에
│   ├── BoardNativeRepository
│   └── UserRepository
├── dto/                      # 모든 DTO가 한 곳에
│   ├── BoardRequest
│   └── UserRequest
└── util/
    └── MyDateUtil

// 문제점: Board 관련 파일들이 여러 패키지에 흩어져 있음
// BoardController ← controller 패키지
// Board          ← model 패키지  
// BoardRepository ← repository 패키지
// BoardRequest   ← dto 패키지

도메인 중심 구조로 전환

 

도메인이란?

도메인(Domain)은 소프트웨어가 해결하려는 문제 영역이나 비즈니스 영역을 의미

 

블로그 시스템의 도메인

Board (게시글) 도메인
   - 게시글 작성, 수정, 삭제, 조회
   - 조회수 증가, 인기글 선정
   - 게시글 상태 관리 (공개/비공개/임시저장)

User (사용자) 도메인
   - 회원가입, 로그인, 프로필 관리
   - 권한 관리, 활동 이력

Comment (댓글) 도메인
   - 댓글 작성, 수정, 삭제
   - 대댓글, 댓글 좋아요

Category (카테고리) 도메인
   - 카테고리 생성, 관리
   - 게시글 분류

 

변경할 프로젝트 형태

com.tenco.blog
├── board/                    # Board 도메인의 모든 것이 한 곳에
│   ├── Board                 # 엔티티
│   ├── BoardController       # 컨트롤러
│   ├── BoardPersistRepository # 리포지토리
│   └── BoardRequest          # DTO
├── user/                     # User 도메인의 모든 것이 한 곳에
│   ├── User
│   ├── UserController
│   ├── UserRepository
│   └── UserRequest
└── utils/                    # 공통 기능
    └── MyDateUtil

// 장점: Board 관련된 모든 클래스가 board 패키지에 응집

 

- board/Board.java 코드

package com.tenco.blog.board;

import com.tenco.blog.util.MyDateUtil;
import jakarta.persistence.*;
import lombok.Data;

import java.sql.Timestamp;

@Data // get,set, toString ..
// @Entity - JPA가 이 클래스를 데이터베이스 테이블과 매핑하는 객체로 인식하게 설정
// 즉, 이 어노테이션이 있어야 JPA가 관리 함
@Entity
@Table(name = "board_tb")
public class Board {

    // @id : 이 필드가 기본키임을 설정 함
    @Id
    // IDENTITY 전략: 데이터베이스게 기본 AUTO_INCREMENT 기능 사용
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
    private String title;
    private String content;
    private Timestamp createdAt;

    // createdAt -> 포멧 하는 메서드 만들어 보기
    public String getTime() {
        return MyDateUtil.timestampFormat(createdAt);
    }

}
중요 정리

// @Entity - JPA가 이 클래스를 데이터베이스 테이블과 매핑하는 객체로 인식하게 설정
// 즉, 이 어노테이션이 있어야 JPA가 관리 함

// @id : 이 필드가 기본키임을 설정 함
 
// IDENTITY 전략: 데이터베이스게 기본 AUTO_INCREMENT 기능 사용

 

board/BoardController.java 코드

package com.tenco.blog.board;


import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Slf4j
@Controller // IoC
@RequiredArgsConstructor // DI
public class BoardController {
    // DI
    private final BoardNativeRepository boardNativeRepository;

    /**
     * 게시글 작성 화면 요청
     * @return 페이지 반환
     * 주소설계 : http://localhost:8080/board/save-form
     */
    @GetMapping("/board/save-form")
    public String saveForm() {

        return "board/save-form";
    }

    /**
     * 게시글 작성 기능 요청
     * @return 페이지 반환
     * 주소설계 : http://localhost:8080/board/save-form
     */
    @PostMapping("/board/save")
    public String saveProc(
            @RequestParam("username") String username,
            @RequestParam("title") String title,
            @RequestParam("content") String content) {
        log.info("username : " + username);
        log.info("title : " + title);
        log.info("content : " + content);
        // insert + 트랜잭션 처리
        boardNativeRepository.save(title, content, username);
        // redirect <-- 다시 URL 요청 해 !
        //return "redirect:/";
        return "redirect:/";
    }


    /**
     * 게시글 목록 화면 요청
     * 주소설계 : http://localhost:8080/
     */
    @GetMapping({"/", "index"})
    public String list(Model model) {

        List<Board> boardList = boardNativeRepository.findAll();
        model.addAttribute("boardList", boardList);
        return "board/list";
    }


    // 게시글 상세보기 화면 요청
    // http://localhost:8080/board/1
    @GetMapping("/board/{id}")
    public String detailPage(@PathVariable(name = "id") Integer id, Model model) {
        // 유효성 검사 , 인증 검사

        Board board = boardNativeRepository.findById(id);
        model.addAttribute("board", board);

        return "board/detail";
    }


    // /board/{{board.id}}/delete
    @PostMapping("/board/{id}/delete")
    public String deleteProc(@PathVariable(name = "id") Integer id) {
        boardNativeRepository.deleteById(id);

        // PRG 패턴( Post-> Redirect -> Get) 적용
        return "redirect:/";
    }


    // http://localhost:8080/board/1/update-form
    @GetMapping("/board/{id}/update-form")
    public String updateFormPage(@PathVariable(name = "id") Integer id, Model model) {
        // 사용자 에게 해당 게시물 내용을 보여 줘야 한다.

        // 조회 기능 - 게시글 id로
        Board board = boardNativeRepository.findById(id);
        model.addAttribute("board", board);

        return "board/update-form";
    }

    // /board/{id}/update
    @PostMapping("/board/{id}/update")
    public String updateProc(@PathVariable(name = "id") Integer id,
                         @RequestParam(name = "username") String username,
                         @RequestParam(name = "title") String title,
                         @RequestParam(name = "content") String content) {

        log.info("username : " + username);
        log.info("title : " + title);
        log.info("content : " + content);
        log.info("id : " + id);

        boardNativeRepository.updateById(username, title, content, id);
        // 게시글 수정 완료 ---> 게시글 목록, 게시글 상세보기 화면
        // 리다이렉트는 뷰 리졸브 동작이 아닌 (내부 파일 찾는 것이 아니고)
        // 그냥 새로은 HTTP Get 요청이다.
        return "redirect:/board/" + id;
    }

}
중요 정리

@Controller // IoC (Inversion of Control): 제어의 역전
//주도권을 내가 아닌 프레임워크(Spring)가 갖는 것

@RequiredArgsConstructor // DI(Dependency Injection): 의존성 주입
// 필요한 부품을 외부에서 끼워주는 것

 // PRG 패턴( Post-> Redirect -> Get) 적용

 

board/BoardNativeRepository.java코드

package com.tenco.blog.board;


import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository // Ioc + DI
@RequiredArgsConstructor
public class BoardNativeRepository {

    // EntityManager: JPA 핵심 인터페이스
    // 데이터베이스와 모든 작업을 담당
    private final EntityManager em;

    // 트랜잭션 처리
    @Transactional
    public void save(String title, String content, String username) {
        Query query = em.createNativeQuery("insert " +
                "into board_tb(title, content, username, created_at) values (?, ?, ?, now())");

        query.setParameter(1, title);
        query.setParameter(2, content);
        query.setParameter(3, username);

        query.executeUpdate();
    }

    // 게시글 목록 조회 메서드
    public List<Board> findAll() {
        String sql = """
                select * from board_tb order by id desc
                """;
        // while(rs.next)  { Board board = new Board(); board.setTile(rs.getString("title)))}
        Query query = em.createNativeQuery(sql, Board.class);
        return query.getResultList();
    }

    // 게시글 상세 보기 (특정 ID로 조회)
    public Board findById(Integer id) {
        String strQuery = """
                select * from board_tb where id = ?
                """;
        try {
            Query query = em.createNativeQuery(strQuery, Board.class);
            query.setParameter(1, id);
            return (Board) query.getSingleResult();
        } catch (Exception e) {
            return null;
        }
    }

    // 게시글 삭제 하기
    @Transactional
    public void deleteById(Integer id) {
        Query query = em.createNativeQuery("delete from board_tb where id = ?");
        query.setParameter(1, id);
        query.executeUpdate();
    }

    // 게시글 수정하기
    @Transactional
    public boolean updateById(String username, String title, String content, Integer id) {

        String queryStr = """
                update board_tb set username = ?, title = ?, content = ? where id = ?
                """;
        Query query = em.createNativeQuery(queryStr);
        query.setParameter(1, username);
        query.setParameter(2, title);
        query.setParameter(3, content);
        query.setParameter(4, id);

        int rows = query.executeUpdate();
        if(rows > 0) {
            return true;
        } else {
            return false;
        }
    }

}
중요정리

@Repository // Ioc + DI

// EntityManager: JPA 핵심 인터페이스
// 데이터베이스와 모든 작업을 담당
private final EntityManager em;