Spring Boot에서 Liquibase – 개발자 가이드

발행: (2026년 3월 10일 PM 05:05 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

Liquibase가 무엇이며 왜 중요한가

Liquibase는 본질적으로 데이터베이스 스키마를 위한 Git과 같습니다. SQL 스크립트를 수동으로 실행하고 모두가 올바르게 실행하기를 기대하는 대신, 구조화된 형식으로 변경 사항을 정의하고 Liquibase가 적용된 내용을 추적합니다.

예시 XML Changeset



    
        
    

Liquibase는 DATABASECHANGELOG 라는 테이블에 적용된 마이그레이션을 기록하여 다음을 보장합니다:

  • 변경 사항은 한 번만 적용됩니다
  • 적용 순서가 유지됩니다
  • 롤백이 가능합니다

주요 장점

  • 일관된 환경 – 개발, 스테이징, 프로덕션이 동기화된 상태를 유지
  • 마이그레이션 히스토리 – 누가 언제 무엇을 변경했는지 확인 가능
  • 자동 마이그레이션 – Spring Boot가 시작 시 실행
  • 롤백 – 필요 시 안전하게 변경 사항을 되돌릴 수 있음

Spring Boot에서 Liquibase 설정하기

Maven 의존성

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

Gradle 의존성

implementation 'org.liquibase:liquibase-core'

Spring Boot는 Liquibase를 자동으로 감지하고 마이그레이션을 실행합니다.

설정 (application.yml)

spring:
  liquibase:
    change-log: "classpath:/db/changelog/changelog.xml"
    contexts: "dev"

일반적인 프로젝트 구조

src/main/resources
 └─ db
    └─ changelog
        └─ changelog.xml

마이그레이션 작성하기

전체 XML Changelog 예시 (changelog.xml)



    
        
            
                
                    
                
                
                    
                    
                
            

            
                
                    
                
            

            
                
                    
                
                
                    
                
            

            
                
                    
                    
                
            

포맷된 SQL Changeset 예시

-- liquibase formatted sql
-- changeset dev:create-users
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP
);

-- changeset dev:add-email-index
CREATE INDEX idx_users_email ON users(email);

-- changeset dev:add-phone-column
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20);
-- rollback: ALTER TABLE users DROP COLUMN phone_number;

모범 사례

  • 실행된 마이그레이션은 절대 수정하지 않기 – 마이그레이션은 불변이어야 합니다.
  • 하나의 changeset = 하나의 변경 – 각 changeset은 하나의 테이블, 컬럼, 인덱스 등 하나의 작업에 집중시킵니다.
  • 파일에 번호 매기기 – 여러 파일을 사용할 경우 Git 충돌을 방지하기 위해 번호를 붙입니다.
  • 로컬에서 테스트 – DB를 삭제하고 애플리케이션을 실행해 처음부터 마이그레이션이 정상 동작하는지 확인합니다.
  • 프로덕션에서는 신중히 – 대형 테이블에 대해 ALTER COLUMN TYPE, DROP COLUMN 또는 NOT NULL 제약을 추가하는 작업은 충분한 계획 없이 수행하지 마세요.

기능 비교

기능LiquibaseFlyway
XML/YAML/JSON
SQL
롤백제한적

Flyway는 더 간단하지만, Liquibase는 엔터프라이즈 환경에 필요한 유연성을 제공합니다.

결론

Liquibase는 환경 간 데이터베이스 스키마를 일관되게 유지하는 데 큰 도움이 됩니다. 다음 골든 룰을 지키세요:

  • 불변의 마이그레이션
  • changeset당 하나의 변경
  • 로컬에서 마이그레이션 테스트

읽어 주셔서 감사합니다!

0 조회
Back to Blog

관련 글

더 보기 »

Archify 소개: 아키텍처 아이디어에서 Spring Boot 코드까지

문제 모든 백엔드 개발자는 이 순간을 경험해 본 적이 있다: 새로운 프로젝트를 시작하면서 이미 아키텍처를 염두에 두고—아마도 간단한 REST 서비스와 몇 개의 엔드포인트만을 생각하고—시작한다. 하지만 프로젝트가 진행될수록 요구사항이 늘어나고, 복잡도가 급격히 증가한다. ### 원인 1. **초기 설계 부족** - 프로젝트 초기에 충분한 도메인 모델링과 데이터 흐름 설계를 하지 않으면, 나중에 구조를 바꾸는 데 큰 비용이 든다. 2. **기능 폭발** - 초기에는 “읽기 전용” API만 필요했지만, 곧 쓰기, 인증, 권한, 실시간 알림 등 다양한 기능이 추가된다. 3. **기술 스택 변화** - 새로운 라이브러리나 프레임워크가 등장하면서 기존 코드를 교체하거나 통합해야 하는 상황이 발생한다. ### 해결책 #### 1. 도메인 중심 설계(Domain‑Driven Design) 적용 - **Bounded Context**를 명확히 정의하고, 각 컨텍스트마다 독립적인 모델과 서비스 계층을 만든다. - **Entity**, **Value Object**, **Aggregate** 등을 활용해 비즈니스 로직을 캡슐화한다. #### 2. 레이어드 아키텍처 도입 - **Presentation Layer** (Controller / Handler) – HTTP 요청을 받아 DTO 로 변환하고, 서비스에 위임한다. - **Application Layer** – 트랜잭션 경계와 워크플로우를 정의한다. - **Domain Layer** – 핵심 비즈니스 로직과 도메인 규칙을 구현한다. - **Infrastructure Layer** – DB, 메시지 브로커, 외부 API 등 기술적 세부 사항을 담당한다. #### 3. 인터페이스와 의존성 역전(Dependency Inversion) - 상위 레이어가 하위 레이어에 의존하지 않도록 **Repository Interface**, **Service Interface** 등을 정의하고, 구현은 인프라 레이어에 둔다. - 스프링에서는 `@Autowired` 대신 **Constructor Injection**을 사용하고, 테스트 시에는 **Mock** 구현체를 주입한다. #### 4. 모듈화와 마이크로서비스 전략 - 시스템이 일정 규모를 넘어가면 **Domain‑Driven Microservices** 혹은 **Modular Monolith** 로 전환한다. - 각 모듈(또는 서비스)은 독립적인 데이터베이스 스키마와 배포 파이프라인을 갖는다. #### 5. 자동화된 테스트와 CI/CD 파이프라인 - **Unit Test**, **Integration Test**, **Contract Test**(예: Spring Cloud Contract) 를 레이어별로 작성한다. - GitHub Actions, GitLab CI 등으로 **빌드 → 테스트 → 배포** 흐름을 자동화한다. #### 6. 문서화와 코드 규칙 - OpenAPI/Swagger 로 API 스펙을 정의하고, **API‑First** 접근법을 채택한다. - 코드 스타일은 **Checkstyle**, **Spotless**, **EditorConfig** 로 일관성을 유지한다. ### 예시 코드 (Spring Boot) ```java // Domain Layer @Entity public class Order { @Id @GeneratedValue private Long id; private LocalDateTime orderDate; @Embedded private Money totalAmount; // 비즈니스 메서드 public void addItem(Product product, int quantity) { … } } // Application Layer @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepo; private final PaymentGateway paymentGateway; @Transactional public OrderDto placeOrder(CreateOrderCommand cmd) { Order order = new Order(); // 도메인 로직 수행 orderRepo.save(order); paymentGateway.charge(order.getTotalAmount()); return OrderMapper.toDto(order); } } // Infrastructure Layer @Repository public interface OrderRepository extends JpaRepository<Order, Long> {} // Presentation Layer @RestController @RequestMapping('/api/orders') @RequiredArgsConstructor public class OrderController { private final OrderService orderService; @PostMapping public ResponseEntity<OrderDto> create(@RequestBody @Valid CreateOrderCommand cmd) { OrderDto result = orderService.placeOrder(cmd); return ResponseEntity.status(HttpStatus.CREATED).body(result); } } ``` ### 마무리 프로젝트 초기에 **아키텍처 설계**와 **도메인 모델링**에 충분한 시간을 투자하면, 이후 기능 추가나 기술 교체가 훨씬 수월해진다. 레이어드 아키텍처와 DDD 원칙을 적용하고, 의존성 역전 및 자동화된 테스트를 기반으로 하면, 복잡도가 증가해도 유지보수 가능한 코드를 유지할 수 있다. **핵심 포인트** - 초기 설계에 투자 → 장기 비용 절감 - 레이어와 인터페이스로 책임 분리 - 테스트와 CI/CD 로 변경 위험 최소화 - 필요 시 마이크로서비스 혹은 모듈형 모노리식으로 확장

Java에서 Two Sum 문제

Two‑Sum 문제 – Java에서 다양한 접근법 Two‑Sum 문제는 개발자 직무 인터뷰에서 가장 흔히 나오는 질문 중 하나입니다. 정수 배열이 주어졌을 때…