
이번 강의는 예외 처리에 대한 개념 학습 이후 Spring Boot를 이용한 CRUD 애플리케이션의 구조를 설계하는 과정을 배운다.
데이터베이스와 연동하기 전 단계에서 MVC 패턴과 Spring Framework의 핵심 구성 요소, 그리고 적용되는 디자인 패턴을 배웠다.

출처: https://devlog-wjdrbs96.tistory.com/141
주의할 점
try {
// 예외가 발생할 가능성이 있는 코드
} catch (ExceptionType1 e1) {
// ExceptionType1 예외가 발생했을 경우 실행할 코드
} catch (ExceptionType2 e2) {
// ExceptionType2 예외가 발생했을 경우 실행할 코드
} finally {
// 예외 발생 여부와 상관없이 항상 실행
}
public class ExceptionApp {
public static void main(String[] args) {
System.out.println("debug >>>> main start");
String[] strAry = {"jslim","inspire","lgcns"};
try {
for(int idx = 0; idx <= strAry.length; idx++) {
System.out.println(strAry[idx]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("debug >>>> 예외 발생 시에만 수행");
e.printStackTrace();
} finally {
System.out.println("debug >>>> 예외 발생 여부와 상관 없이 수행");
}
System.out.println("debug >>>> main end");
}
}
예외를 호출한 쪽으로 다시 예외를 떠넘기는 것
public void xxxxxx() throws xxxxException {
예외코드
}
// 사용자 정의 예외 클래스
public class xxxxException extends Exception {
}
의도적으로 예외 발생
package features.exception;
import features.util.InspireException;
public class exceptionDemo {
public void first(int x) throws InspireException {
System.out.println("debug >>>> first method start");
try {
if(x < 0) {
throw new InspireException("양의 정수만 가능합니다");
}
} finally {
System.out.println("debug >>>> first method end");
}
}
}
---> 보완할 부분
Controller → Service → Repository(DAO)의 3층 구조URL → 컨트롤러 매핑을 Key-Value형으로 관리Factory를 통해 생성되고 프론트가 호출MyBatis / JPA(Hibernate): 두 가지 대표 ORM 프레임워크 (구현체)
Repository는 인터페이스만 선언하고Repository에서 Entity를 조회/저장하고
DTO를 통해 컨트롤러와 서비스 간에 데이터 교환
├─ controller
│ └─ BlogController
├─ service
│ └─ BlogService
├─ repository
│ └─ BlogRepository
├─ domain
│ ├─ dto
│ └─ entity
├─ util
│ └─ ScannerUtil
└─ view
└─ ConsoleView
cf. scanner → next / nextLine : 공백 포함
| 용어 | 뜻 |
|---|---|
| DTO (Data Transfer Object) | 데이터를 담아 계층 간에 전달하는 용도로만 쓰임 비즈니스 로직 없음 필드 + getter/setter만 있음 |
| DAO (Data Access Object) | 데이터베이스에 직접 접근해서 CRUD 쿼리를 실행하는 객체 실제 데이터를 찾아오는 것 |
| ORM (Object-Relational Mapping) | DB의 테이블(행/열)을 자바의 객체(Object)로 자동 매핑해주는 기술/도구 |
DB 테이블 (row)
↕ // ORM이 자동 변환
Entity 객체 (Dao/Repository 내부에서 다룸)
↓ // 변환해서 담기
DTO (Controller ↔ Service ↔ View 사이를 이동)
public void landingPage() {
while(true) {
System.out.println();
System.out.println(">>>> Inspire Camp Blog Ver(1.0) <<<<");
System.out.println("1. 전체 검색");
System.out.println("2. 게시글 상세보기");
System.out.println("3. 입력화면");
System.out.println("4. 수정화면");
System.out.println("5. 게시글 삭제");
System.out.println("6. 검색");
System.out.println("99. 프로그램 종료");
try {
System.out.print("원하시는 메뉴 번호를 입력하세요 : ");
int number = Integer.parseInt(scan.nextLine());
// case가 많을때
switch(number) {
case 99:
exit();
break;
default:
break;
}
} catch(Exception e) {
System.out.println(">>> 예외 발생으로 인해 프로그램을 종료합니다.");
break;
}
}
}
main()
→ BlogReactView.landingPage() (사용자 입력 받는 화면)
→ BlogFrontController.list() (요청을 받아서 분배)
→ BlogBeanFactory.getBean() (컨트롤러 객체를 찾아줌)
→ ListController.list() (컨트롤러 PL)
→ BlogReactService.list() (비즈니스 로직 BL)
→ BlogReactDao.findByAll() (데이터 저장소 접근)
controller
service
Repository/dao
백엔드의 각 계층은 서로간만 정보전달이 가능하다.
ListController가 BlogReactDao를 몰라야 함BlogRequestDTOpackage features.blogs.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
public class BlogRequestDTO {
private int id;
private String title;
private String content;
private String email;
}
BlogResponseDTOpackage features.blogs.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class BlogResponseDTO {
private int status;
private String message;
////////////////////////////////////////
private Integer blogId;
private String title, content, email;
private Integer viewCnt;
}
import features.blogs.view.BlogReactView;
public class BlogReactApp {
public static void main(String[] args) {
BlogReactView view = new BlogReactView();
view.landingPage();
}
}
BlogFrontController(ListController)controller : 다운캐스팅BlogBeanFactory.getInstance() : 객체를 딱 하나만 만들고 계속 재사용package features.blogs.facade;
import java.util.List;
import javax.swing.ListCellRenderer;
import features.blogs.controller.ListController;
import features.blogs.domain.dto.BlogResponseDTO;
import features.blogs.factory.BlogBeanFactory;
public class BlogFrontController {
private BlogBeanFactory factory;
public BlogFrontController() {
factory = BlogBeanFactory.getInstance(); // 싱글톤 패턴
}
public List<BlogResponseDTO> list(String endPoint) {
System.out.println(
"debug >>>> front controller endPoint : " + endPoint
);
Object controller = factory.getBean(endPoint);
return ((ListController)controller).list();
}
}
BlogBeanFactory.getInstance()BlogBeanFactory는 "어떤 endPoint에 어떤 컨트롤러가 연결되는지" 정보를 담고 있는 창고클라이언트 요청 (예: GET /blogs/list)
↓
DispatcherServlet
← 앱 전체에 "딱 하나"만 존재 (싱글톤처럼 동작)
↓
URL을 보고 어떤 컨트롤러 메서드를 실행할지 찾음
↓
HandlerMapping
↓
@Controller 의 해당 메서드 실행
↓
결과를 다시 DispatcherServlet이 받아서 응답으로 내보냄
public class BlogBeanFactory {
private Map<String, Object> map;
private static BlogBeanFactory instance;
private BlogBeanFactory() {
}
public static BlogBeanFactory getInstance() {
if(instance == null) {
instance = new BlogBeanFactory();
}
return instance;
}
}
package features.blogs.controller;
import java.util.List;
import features.blogs.domain.dto.BlogResponseDTO;
import features.blogs.service.BlogReactService;
public class ListController {
private BlogReactService service;
public ListController() {
}
public ListController(BlogReactService service) {
this.service = service;
}
// view 에게 응답하는 역할의 메서드
public List<BlogResponseDTO> list() {
System.out.println(
"debug >>>> list controller list() "
);
return service.list();
}
}
package features.blogs.service;
import java.util.List;
import features.blogs.domain.dto.BlogRequestDTO;
import features.blogs.domain.dto.BlogResponseDTO;
import features.blogs.repository.BlogReactDao;
public class BlogReactServiceImpl implements BlogReactService {
private BlogReactDao dao;
public BlogReactServiceImpl() {
}
public BlogReactServiceImpl(BlogReactDao dao) {
this.dao = dao;
}
@Override
public List<BlogResponseDTO> list() {
System.out.println("debug >>>> blog service list ");
return dao.findByAll();
}
@Override
public BlogResponseDTO read(int blogId) {
throw new UnsupportedOperationException(
"Unimplemented method 'read'"
);
}
@Override
public int insert(BlogRequestDTO request) {
throw new UnsupportedOperationException(
"Unimplemented method 'insert'"
);
}
@Override
public int update(BlogRequestDTO request) {
throw new UnsupportedOperationException(
"Unimplemented method 'update'"
);
}
@Override
public int delete(int blogId) {
throw new UnsupportedOperationException(
"Unimplemented method 'delete'"
);
}
@Override
public List<BlogResponseDTO> search(BlogRequestDTO request) {
throw new UnsupportedOperationException(
"Unimplemented method 'search'"
);
}
}
package features.blogs.repository;
import java.util.ArrayList;
import java.util.List;
import features.blogs.domain.dto.BlogResponseDTO;
public class BlogReactDao {
private List<BlogResponseDTO> blogs;
public BlogReactDao() {
blogs = new ArrayList<>(List.of(
BlogResponseDTO.builder()
.blogId(1)
.title("react")
.content("state")
.email("lim")
.viewCnt(10)
.build(),
BlogResponseDTO.builder()
.blogId(2)
.title("java")
.content("oop")
.email("kim")
.viewCnt(20)
.build(),
BlogResponseDTO.builder()
.blogId(3)
.title("spring")
.content("mybatis")
.email("lee")
.viewCnt(30)
.build(),
BlogResponseDTO.builder()
.blogId(4)
.title("docker")
.content("devops")
.email("park")
.viewCnt(40)
.build(),
BlogResponseDTO.builder()
.blogId(5)
.title("msa")
.content("kafka")
.email("lim")
.viewCnt(50)
.build()
));
}
public List<BlogResponseDTO> findByAll() {
System.out.println(
"debug >>>> blog dao findByAll() "
);
return blogs;
}
}
package features.blogs.repository;
import features.blogs.domain.dto.BlogResponseDTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/*
repository, dao
- data access logic
- JDBC를 기본으로해서 Object Relationship Mapping
(mybatis, JPA) 확장
- 원격에 있는 database와 통신을 통해서
CRUD 작업을 전담하는 객체
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
public class BlogRepository {
private BlogResponseDTO[] list
= new BlogResponseDTO[10];
public BlogResponseDTO[] blogs() {
System.out.println(
"debug >>>> BlogRepository blogs() "
);
return list;
}
}
| 개념 | 설명 |
|---|---|
| 프론트 컨트롤러 | 요청을 받아서 그 요청이 어떤 컨트롤러에 매핑되는지 찾아서 컨트롤러에 요청을 전달, 다시 그 응답을 앱 계층에 전달 |
| 싱글톤 | 한번에 하나의 인스턴스만 생성 → 모두 동일한 인스턴스를 공유하도록 강제 가능 |
| 컨트롤러 | 요청을 받아 서비스에 위임하고, 서비스가 준 결과를 그대로 응답으로 반환 |
| DTO | 컨트롤러와 서비스를 오가는 데이터가 담기는 그릇 • 응답 줄 때: service 처리 결과를 ResponseDTO에 담아서 view에 전달• 요청 올 때: 사용자 입력을 RequestDTO에 담아서 service한테 전달 |
| 서비스 | 실제 비즈니스 로직을 수행 |
| DAO, Repository | ORM을 통해 데이터베이스를 읽고 Entity 실제 데이터를 읽어옴 읽기, 쓰기, 수정, 삭제 = CRUD 수행 |
사실 정확하게 서비스 계층의 비즈니스 로직에 오버라이드를 적용하는 구성은 이해하지 못했다.
다만 오늘은 스프링의 로직을 이해하는데 집중했다.
내일 실습을 통해서 DAO에 대해서 다시 개념을 보완해봐야겠다.