KDT:::Sparta MSA 아키텍처 3기(week01)
스프링을 사용하는 이유
- 스프링 프레임워크는 자바(Java) 기반의 애플리케이션을 개발하는 데 필요한 거의 모든 것을 지원하는 오픈소스 프레임워크이다.
- 웹 개발뿐만 아니라, 데이터 처리, 보안, 배치(batch) 작업 등 기업 환경의 복잡한 시스템을 구축하는 데 필요한 포괄적인 프로그래밍 및 설정 모델을 제공한다.
스프링 3대 핵심 특징
1. IoC (Inversion of Control) / DI (Dependency Injection): 제어의 역전 / 의존성 주입
- 객체의 생성과 생명주기 관리를 스프링 컨테이너가 대신해 줍니다. 개발자는 필요한 객체를 선언만 하면, 스프링이 알아서 주입(Injection)해준다.
2. AOP (Aspect-Oriented Programming): 관점 지향 프로그래밍
- 애플리케이션 전반에 걸쳐 공통적으로 적용되는 기능(예: 로깅, 보안, 트랜잭션)을 비즈니스 로직과 분리하여 모듈화한다.
- 메인 비즈니스 코드에는 영향을 주지 않으면서, 필요한 부분에 공통 기능을 끼워넣을 수 있다.
3. PSA (Portable Service Abstraction): 일관된 서비스 추상화
- 데이터베이스 접근 방식(JPA, JDBC 등), 트랜잭션 처리 등 다양한 기술 구현체들을 스프링이 제공하는 일관된 방식으로 사용할 수 있다.
- 기술이 바뀌더라도 코드 수정없이 바꿀 수 있다.
현재 실무에서 사용 중인 ProC와 다른점은?
| 구분 | POJO (Plain Old Java Object) | ProC |
|---|---|---|
| 언어 | Java | C + SQL (Oracle 전용) |
| 목적 | 프레임워크에 독립적인 객체 지향 코드 작성 | C 코드 안에 SQL을 직접 포함해서 DB 연동 |
| 철학 | 순수 자바 코드로 비즈니스 로직만 작성 | SQL을 C 프로그램에 삽입해서 성능 높은 DB 프로그램 작성 |
| 의존성 | 프레임워크에 의존하지 않음 | Oracle ProC Precompiler에 의존 |
| 사용 맥락 | 스프링, JPA, Hibernate 등에서 핵심 철학 | 오래된 금융권/레거시 시스템의 DB 프로그래밍 |
디렉토리 구조 예시
src/main
├─ java/com/tmp
│ ├─ TempApplication.java
│ ├─ common # 📂 여러 도메인에서 공통으로 사용하는 코드
│ ├─ domain # 📌 도메인별 패키지
│ │ ├─ category/Category.java # 도메인명/데이터베이스 테이블과 매핑되는 객체클래스 .java
│ │ ├─ product/Product.java
│ │ ├─ order/Order.java
│ │ └─ refund/Refund.java
│ ├─ dto #데이터 전송 객체 (Request/Response)
│ │ ├─ category/CategoryDtos.java
│ │ ├─ product/ProductDtos.java
│ │ ├─ order/OrderDtos.java
│ │ └─ refund/RefundDtos.java
│ ├─ repository # 데이터베이스 접근 로직 (JPA 인터페이스)
│ │ ├─ CategoryRepository.java
│ │ ├─ ProductRepository.java
│ │ ├─ OrderRepository.java
│ │ └─ RefundRepository.java
│ ├─ service # 관련 비즈니스 로직 구현
│ │ ├─ CategoryService.java
│ │ ├─ ProductService.java
│ │ ├─ OrderService.java
│ │ └─ RefundService.java
│ └─ web
│ ├─ CategoryController.java
│ ├─ ProductController.java
│ ├─ OrderController.java
│ └─ RefundController.java
└── resources
├── db/migration/ # DB 마이그레이션 스크립트 (Flyway, Liquibase) : 파일명명규칭 !! V<버전>__<설명>.sql
├── application.yml # 애플리케이션 주요 설정 파일
└── static/ # CSS, JS, 이미지 등 정적 리소스
ORM
- 객체-관계 불일치’ 문제를 해결하기 위해 등장한 기술이다.
- 객체와 DB 테이블을 자동으로 매핑하여, 개발자가 SQL 없이 객체 중심으로 데이터를 다룰 수 있게 해줍니다.
ORM 장점
- 생산성 향상: SQL보다 객체 중심의 코드로 비즈니스 로직에 집중할 수 있습니다.
- 유지보수 용이: 객체 모델만 수정하면 되므로 관리가 편합니다.
- DB 독립성: 특정 데이터베이스에 종속되지 않는 코드를 작성할 수 있습니다.
- 고급 기능: Hibernate는 지연 로딩(Lazy Loading), 캐싱 등 성능 최적화를 위한 고급 기능을 제공합니다.
Spring Data JPA
- ‘벤더 종속성(Vendor Lock-in)’ 문제가 있었습니다. 다른 ORM 기술로 전환하려면 코드를 전부 수정해야 하는 문제 해결
- @Entity, @Id 같은 어노테이션과 persist(), find() 같은 메서드를 표준으로 정의합니다.
- 실제 동작하는 코드가 아니라, ORM 프레임워크들이 따라야 할 설계도이다.
- JPA (설계도): 데이터베이스 연동 기술에 대한 표준 명세(인터페이스)
- Hibernate (실제 일꾼): JPA라는 설계도를 보고 실제로 구현한 가장 유명한 구현체
- Spring Data JPA (편의 도구): Hibernate 같은 JPA 구현체를 더 쉽고 편하게 사용하도록 한 번 더 감싸서,
Repository인터페이스만으로도 개발이 가능하게 만든 스프링 프레임워크의 모듈
Hibernate
- ORM 개념을 구현한 가장 대표적인 프레임워크
- 객체 중심 코드를 작성하면, Hibernate가 적절한 SQL을 생성하고 실행하여 복잡한 데이터 변환 과정을 처리한다.
데이터 로딩
- 객체지향 프로그래밍(OOP)과 관계형 데이터베이스(RDB)가 데이터를 바라보는 방식이 근본적으로 다르다
- 객체지향에서는 (.) 하나로 객체에 접근할 수 있다.
- 그러나, 데이터베이스에서는 테이블로 분리되어있고 참조를 위해서는 JOIN과 같은 별도의 연산이 필요하다.
딜레마
- 개발자가 userRepository.findById(1L)로 User 객체 하나를 요청했을 때,
- “User와 연관된 Purchase 데이터도 지금 당장 함께 가져와야 할까, 아니면 나중에 진짜 필요하다고 할 때 가져올까?”
로딩전략
즉시 로딩(Eager Loading) : 필요할때만 부른다.
- 혹시 모르니 무조건 함께 가져오자!” 라는 전략.
JOIN을 사용해 한 번에 모든 데이터를 가져온다.지연 로딩(Lazy Loading) : 무조건 함께 부른다.
- “일단 급한 것만 주고, 필요하다고 하면 그때 가서 가져다주자!” 라는 전략.
User만 먼저 가져오고,Purchase는 나중에 별도 쿼리로 가져온다.
어노테이션 Builder
- 객체를 만들 때 생성자 대신 가독성 좋게 만들도록 도와주는 Lombok 기능
🚫 기존 방식 (Builder 없을 때)
Category category = new Category("전자제품", "가전 및 주변기기", null); - 생성자에 어떤 값이 어떤 필드인지 한눈에 안 들어옴
- 인자가 많을수록 순서 헷갈리기 쉬움
- 선택적으로 필드 일부만 채우기 어려움
✅ Builder 방식
Category category = Category.builder() .name("전자제품") .description("가전 및 주변기기") .parentId(null) .build(); - 각 필드 이름이 명확하게 드러남
- 순서 상관없이 값 설정 가능
- 선택적 필드만 세팅하기 쉬움
- 메서드 체인 형식으로 보기 깔끔함
생성자 위에 Builder 를 쓰는 경우
```java @Table @Entity @Getter @DynamicInsert @DynamicUpdate @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) Long id;
@Column(nullable = false) String name;
@Column(nullable = false) String email;
@Column(nullable = false) String passwordHash;
@Column(nullable = false, updatable = false) @CreationTimestamp LocalDateTime createdAt;
@Column(nullable = false) @UpdateTimestamp LocalDateTime updatedAt;
@Builder // ✅ 생성자에 Builder 사용 : 그 생성자의 파라미터만 빌더로 사용 public User( String name, String email, String passwordHash ) { this.name = name; this.email = email; this.passwordHash = passwordHash; }
}
* 이 생성자에 있는 3개의 필드만 빌더로 만들겠다는 뜻입니다.
* 스프링/JPA용 엔티티에서 자주 이렇게 쓰는 이유는, id, createdAt, updatedAt 같은 필드는 DB에서 자동 생성되기 때문에 빌더로 입력받을 필요가 없기 때문.
```java
User user = User.builder()
.name("홍길동")
.email("hong@example.com")
.passwordHash("hashed1234")
.build();
// 위 코드를 실행하면 롬복이 자동으로 아래 생성자를 호출 👇
new User("홍길동", "hong@example.com", "hashed1234");
클래스에 Builder 를 쓰는 경우
@Builder
public class User {
Long id;
String name;
String email;
String passwordHash;
LocalDateTime createdAt;
LocalDateTime updatedAt;
}
- 빌더로 모든 필드를 세팅할 수 있게 된다. 그러나 Entity에서는 이게 좋지 않다.
- id, createdAt, updatedAt은 DB가 자동으로 채워야 할 필드인데 빌더로 사용자가 잘못 세팅할 수도 있기 때문
- 실무에서는 엔티티에서
@Builder는 생성자에만 붙이는 게 안전한 패턴이다.