DevLog

병원 예약 시스템 Part 1 - 프로젝트 세팅과 기술 선정 이유 (MyBatis vs JPA)

기마니 2025. 12. 8. 19:00

풀스택 개발자 취업 캠프를 수료한지 이제 4개월 차^_ㅠ 취준생 기간이 짧으면 좋겠지만 내 마음대로 되는 건 아니니 이 기간을 좀 알차게 쓰고 싶어서 개인 프로젝트를 진행하기로 마음먹었다.

 

면접 보러 다니면서 백엔드 쪽이 약하다는 걸 계속 느꼈다. 그래서 이번 프로젝트는 익숙한 MyBatis + JSP 대신 Spring Boot + JPA 로 도전해보기로 했다.

 

1. MyBatis vs JPA

일단 프로젝트 시작 전에 MyBatis랑 JPA 아키텍처 차이를 비교해봤음(따봉 클로드야 고마워)

MyBatis + JSP 구조: Controller ➡ Service ➡ DAO ➡ Mapper.xml (SQL) ➡ DB

MyBatis로 팀프로젝트를 진행했을 때 테이블을 수정하면  DTO, SQL 쿼리, DAO를 전부 수정해야 해서 굉장히 귀찮았었음

 

Spring Boot + JPA 구조: Controller ➡ Service ➡ Repository (Interface) ➡ Entity ➡ DB

SQL 작성x, 자바 객체(Entity)로 DB 다룸. Repository 는 인터페이스만 선언하면 JpaRepository가 기본적인 CRUD 쿼리를 자동으로 생성해줌

 

둘의 차이를 정리해보면 

-  MyBatis JPA
테이블 생성 CREATE TABLE 쿼리를 직접 작성해서 실행 @Entity 클래스만 작성하면 자동 생성
데이터 추가 INSERT 쿼리를 Mapper.xml에 작성 entity.setXxx() 로 값 설정 후 save() 호출
컬럼 추가  ALTER TABLE + 쿼리 수정 + DTO 수정 Entity 클래스에 필드만 추가하면 끝

 

결론: 초기 개발 단계에서 DB 구조가 자주 바뀔 때 JPA의 ddl-auto: update 옵션이 진짜 편함. Entity 클래스만 수정하고 서버를 재시작하면 테이블 구조가 자동으로 업데이트된다.

이번 프로젝트에서만 해도 테이블 구조를 3번이나 바꿨는데 JPA 덕분에 Entity 클래스만 수정하면 됐다. 만약 SQL을 직접 관리했다면....? 쏘끔찍

 

 


 

2. Spring Boot 프로젝트 세팅

start.spring.io 에서 Gradle 프로젝트로 만들고 Java 21을 선택하고 필요한 의존성들도 추가했음

- Spring Web: REST API를 만들 때 필요

- Spring Data JPA: JPA를 사용하려면 필수

- Oracle Driver: Oracle DB에 연결하기 위한 드라이버

- Lombok: Getter, Setter 같은 반복 코드를 자동으로 생성해주는 라이브러리

- Thymeleaf: HTML 템플릿 엔진 (화면 만들 때 사용)

 


 

3. 데이터베이스 접속 설정

Oracle DB에 프로젝트 전용 계정을 만들었다. SQL Developer에서 계정을 생성하고, Spring Boot 설정 파일(application.yml)에 접속 정보를 입력

spring:
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@localhost:1521/XE
    username: 계정 이름
    password: 비밀번호
  jpa:
    hibernate:
      ddl-auto: update  # 서버 시작 시 Entity 기반으로 테이블 자동 생성/수정
    show-sql: true      # 실행되는 SQL 로그 출력

 

ddl-auto: update 옵션을 켜두면 Entity 클래스를 수정할 때마다 테이블 구조가 자동으로 업데이트 됨

 


 

4. JPA Auditing 활성화 (BaseEntity)

데이터 생성일시를 자동 기록하기 위해

메인클래스에 @EnableJpaAuditing 어노테이션 추가( JPA Auditing 활성화)

 

이제 BaseEntity 클래스를 만들면 모든 테이블에 created_at이 자동으로 추가됨

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
}

 

@MappedSuperclass: 이 클래스가 테이블로 생성되지 않고 상속 받는 Entity에 컬럼만 추가한다는 의미

BaseEntity 테이블이 따로 생기는 게 아니라 User 테이블에도 created_at이 생기고 Doctor 테이블에도 created_at이 자동으로 생기는 거임

 


 

5. 데이터베이스 설계 (Entity)

병원 예약 시스템이니까 환자(User), 진료과(Department), 의사(Doctor), 예약(Reservation) 테이블 생성. JPA를 사용하면 Entity 클래스를 작성하는 것만으로 테이블이 자동으로 생성됨. 

BaseEntity를 만든 다음에는 FK(외래키)가 없는 테이블부터 먼저 만들고 FK가 있는 테이블은 참조하는 테이블을 다 만든 후에 만들어야 함.

 

엔티티 구조

  • BaseEntity: 모든 테이블에 공통으로 들어가는 created_at(생성일시)를 관리하는 부모 클래스.
  • User: 환자 정보. Oracle Sequence(USER_SEQ) 사용
  • Department: 진료과 정보.
  • Doctor: 의사 정보. Department와 N:1 관계(@ManyToOne).
  • Reservation: 예약 정보. 환자와 의사를 참조하며 예약 시간을 관리.

(사실 초기에는 의사 스케줄을 관리하는 DOCTOR_SCHEDULE 테이블도 있었는데 비효율적이라 생각해서 나중에 제거함)

 

 

@Entity
@Table(name = "USERS") // 1. 테이블명 지정
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseEntity { // 2. 상속
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_SEQ_GENERATOR")
    @Column(name = "user_id")
    private Long id;
    
    // ... 나머지 필드
}

 

@Table(name = "USERS"): JPA는 @Entity 클래스 이름(User)을 그대로 테이블 이름으로 사용하는데 Oracle DB에서 USER는 예약어라서 테이블 이름으로 사용할 수 없음. 그래서 에러를 방지하기 위해 USERS라는 이름으로 직접 매핑해야 함

 

extends BaseEntity: User 클래스에는 created_at(생성일시) 필드가 없지만, BaseEntity를 상속받음으로써 DB 테이블에는 해당 컬럼이 자동으로 생성됨. (created_at(생성일시) 필드가 들어가야 하는 Entity는 꼭 써야함 )

 

@NoArgsConstructor(access = AccessLevel.PROTECTED): JPA는 DB에서 데이터를 가져와서 객체를 만들 때 기본 생성자를 사용하여 객체를 먼저 생성한 뒤 값을 채워 넣음. 그래서 기본 생성자가 필요함 (PROTECTED: JPA가 접근할 수 있는 범위를 최소한으로 해 객체의 안정성 보장)

 

@Id: PK 설정. MyBatis는 SQL(mapper.xml) 안에서 WHERE id = #{id} 처럼 직접 PK를 지정하므로, DTO 객체에 @Id 같은 표시를 할 필요가 없다.하지만 JPA는 자바 객체(Entity) 그 자체로 DB 데이터를 관리한다. 따라서 이 객체의 어떤 필드가 PK인지 JPA에게 명확히 알려줘야만 데이터를 구분할 수 있다. 이 표시가 없으면 에러 발생함

 

@Column(name = "user_id"): 컬럼명 매핑. 자바에서는 변수명을 userID처럼 카멜케이스로 쓰는 게 관례지만 DB에서는 user_id처럼 스네이크케이스를 사용한다. 이 둘의 이름이 다를 때 이 필드는 DB의 user_id컬럼이랑 같다고 명시해주는 역할이다. 

만약 @Column(name = "user_id")를 안 쓰면 JPA는 DB에서 ID라는 컬럼을 찾게 된다. 이름이 서로 다를 때는 반드시 매핑을 해줘야 됨.

 


 

6. Repository 계층 구현

엔티티 설계를 마친 후 DB에 접근하기 위한 Repository 인터페이스를 생성해야 함. 상속 받으면 스프링 부트가 내부적으로 구현를 자동으로 생성해서 Bean으로 등록해줌. 기본적인 CRUD는 자동으로 제공되니까 커스텀 메서드가 필요할 때 추가하면 됨.

public interface ReservationRepository extends JpaRepository<Reservation, Long> {
  
}

 

extends JpaRepository<Reservation, Long>: JpaRepository<엔티티타입, PK 타입>

 


 

프로젝트 패키지 구조

이 프로젝트는 Layered Architecture패턴이다.

Layered Architecture는 코드를 계층별로 나누는 방식이라 각 계층이 명확한 책임을 가지고 있어서 코드 관리가 편하다.

라고 클로드가 알려줌.....

 

src/main/java/com/example/demo/

├── api/ REST API 컨트롤러 (JSON 반환) - 프론트엔드와 통신

├── controller/ 화면(View) 컨트롤러 - 서버에서 HTML 생성

├── domain/ Entity 클래스 - DB 테이블과 매핑

├── repository/ DB 접근 인터페이스

└── service/ 비즈니스 로직 (예약 가능 시간 계산, 중복 검증 등)

 

 

 

 

https://github.com/rlaksl/spring-reservation-project