Home > Backend Development > πŸ“š[Backend Development] Spring Boot Service Layer와 ORM μ™„λ²½ μ΄ν•΄ν•˜κΈ°

πŸ“š[Backend Development] Spring Boot Service Layer와 ORM μ™„λ²½ μ΄ν•΄ν•˜κΈ°
Backend Ddevelopment Programming Language Object OOP ORM Spring Boot Service Layer

πŸš€ Spring Boot Service Layer와 ORM μ™„λ²½ μ΄ν•΄ν•˜κΈ°

μ™œ 이 λ¬Έμ„œλ₯Ό μž‘μ„±ν–ˆλ‚˜? πŸ€”
Java Spring Boot λ°±μ—”λ“œ ν”„λ‘œμ νŠΈμ—μ„œ Service Layer의 CRUD λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜λ©΄μ„œ, Response DTO 생성과 λ°˜ν™˜ κ³Όμ •μ—μ„œ ORMκ³Ό μ„œλΉ„μŠ€ 둜직의 핡심을 μ œλŒ€λ‘œ μ΄ν•΄ν•˜μ§€ λͺ»ν•΄ μ •λ¦¬ν•œ ν•™μŠ΅ λ…ΈνŠΈμž…λ‹ˆλ‹€.

🎯 핡심 λͺ©ν‘œ

Service Layerμ—μ„œ Entity β†’ DTO λ³€ν™˜ κ³Όμ •κ³Ό ORM의 역할을 μ™„λ²½νžˆ μ΄ν•΄ν•˜μž!


πŸ“– λͺ©μ°¨

  1. ORM을 μ΄ν•΄ν•˜κΈ° μœ„ν•œ 객체(Object) κ°œλ… 정리
  2. ORMμ΄λž€ 무엇인가?
  3. Spring Boot Service Layerμ—μ„œμ˜ μ‹€μ „ 적용
  4. Entity vs DTO: μ–Έμ œ, μ™œ λ³€ν™˜ν•˜λŠ”κ°€?

🧩 ORM을 μ΄ν•΄ν•˜κΈ° μœ„ν•œ 객체(Object) κ°œλ… 정리

πŸ” 객체(Object)의 두 κ°€μ§€ 관점

1️⃣ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ˜ 객체

// λ‹¨μˆœν•œ 데이터와 λ©”μ„œλ“œμ˜ μ‘°ν•©
Map<String, Object> user = new HashMap<>();
user.put("name", "홍길동");
user.put("age", 25);
  • μ •μ˜: 데이터(속성)와 κΈ°λŠ₯(λ©”μ„œλ“œ)λ₯Ό ν•˜λ‚˜λ‘œ 묢은 ν”„λ‘œκ·Έλž˜λ° λ‹¨μœ„
  • νŠΉμ§•: μ–Έμ–΄λ³„λ‘œ λ‹€μ–‘ν•œ ν˜•νƒœλ‘œ 쑴재 (JavaScript 객체, Python λ”•μ…”λ„ˆλ¦¬ λ“±)

2️⃣ 객체지ν–₯ ν”„λ‘œκ·Έλž˜λ°(OOP)의 객체

// 클래슀 기반의 체계적인 객체
public class User {
    private String name;
    private int age;
    
    public void introduce() {
        System.out.println("μ•ˆλ…•ν•˜μ„Έμš”, " + name + "μž…λ‹ˆλ‹€!");
    }
}

User user = new User(); // ν΄λž˜μŠ€λ‘œλΆ€ν„° μƒμ„±λœ μΈμŠ€ν„΄μŠ€
  • μ •μ˜: 클래슀(섀계도)λ₯Ό 기반으둜 μƒμ„±λœ, μΊ‘μŠν™”/상속/λ‹€ν˜•μ„±/좔상화λ₯Ό λ”°λ₯΄λŠ” 독립적 λ‹¨μœ„
  • νŠΉμ§•: 객체 κ°„ ν˜‘λ ₯κ³Ό μƒν˜Έμž‘μš©μ΄ 핡심

πŸ’‘ 핡심 차이점

| ꡬ뢄 | ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ˜ 객체 | OOP의 객체 |
|β€”β€”|β€”β€”β€”β€”β€”β€”β€”-|β€”β€”β€”β€”|
| 생성 방식 | λ‹€μ–‘ν•œ 방법 | 클래슀 기반 |
| 섀계 원칙 | μžμœ λ‘œμ›€ | OOP 4λŒ€ 원칙 μ€€μˆ˜ |
| λͺ©μ  | 데이터 ꡬ쑰화 | ν˜„μ‹€ 세계 λͺ¨λΈλ§ |


πŸ”— ORMμ΄λž€ 무엇인가?

πŸ“Š ORM의 핡심 κ°œλ…

Object-Relational Mapping: OOP의 객체와 κ΄€κ³„ν˜• λ°μ΄ν„°λ² μ΄μŠ€μ˜ ν…Œμ΄λΈ”μ„ μžλ™μœΌλ‘œ μ—°κ²°ν•΄μ£ΌλŠ” 기술

// λ°μ΄ν„°λ² μ΄μŠ€ ν…Œμ΄λΈ”
/*
users ν…Œμ΄λΈ”
+----+---------+-----+
| id | name    | age |
+----+---------+-----+
| 1  | 홍길동   | 25  |
| 2  | κΉ€μ² μˆ˜   | 30  |
+----+---------+-----+
*/

// ↕️ ORM이 μžλ™ λ§€ν•‘ ↕️

// Java 객체 (Entity)
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private int age;
    
    // getters, setters...
}

🎯 ORMμ—μ„œ λ§ν•˜λŠ” β€œObject”

λ‹΅: OOP의 객체(클래슀 μΈμŠ€ν„΄μŠ€)

  • JPA EntityλŠ” 클래슀 기반으둜 μ •μ˜
  • OOP 원칙을 따라 섀계
  • λ°μ΄ν„°λ² μ΄μŠ€ ν…Œμ΄λΈ”κ³Ό 1:1 λ§€ν•‘λ˜λŠ” 도메인 객체

⚑ Spring Boot Service Layerμ—μ„œμ˜ μ‹€μ „ 적용

πŸ—οΈ 전체 μ•„ν‚€ν…μ²˜ 흐름

Controller β†’ Service β†’ Repository β†’ Database
     ↑           ↓
   DTO        Entity

πŸ’» μ‹€μ œ μ½”λ“œ μ˜ˆμ‹œ

1️⃣ Entity μ •μ˜

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String email;
    private int age;
    
    // constructors, getters, setters...
}

2️⃣ Repository Layer

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
    List<User> findByAgeGreaterThan(int age);
}

3️⃣ Service Layer (핡심! πŸ”₯)

@Service
@Transactional
public class UserService {
    
    private final UserRepository userRepository;
    
    // CREATE: DTO β†’ Entity β†’ DTO
    public UserResponseDto createUser(UserCreateDto createDto) {
        // 1. DTOλ₯Ό Entity둜 λ³€ν™˜
        User user = User.builder()
                .name(createDto.getName())
                .email(createDto.getEmail())
                .age(createDto.getAge())
                .build();
        
        // 2. ORM이 Entityλ₯Ό DB에 μ €μž₯
        User savedUser = userRepository.save(user);
        
        // 3. Entityλ₯Ό Response DTO둜 λ³€ν™˜ν•˜μ—¬ λ°˜ν™˜
        return UserResponseDto.from(savedUser);
    }
    
    // READ: Entity β†’ DTO
    public UserResponseDto getUser(Long userId) {
        // 1. ORM이 DBμ—μ„œ 데이터λ₯Ό μ‘°νšŒν•˜μ—¬ Entity 생성
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException("μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."));
        
        // 2. Entityλ₯Ό Response DTO둜 λ³€ν™˜
        return UserResponseDto.from(user);
    }
    
    // UPDATE: DTO + Entity β†’ DTO
    public UserResponseDto updateUser(Long userId, UserUpdateDto updateDto) {
        // 1. 기쑴 Entity 쑰회
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException("μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."));
        
        // 2. Entity μƒνƒœ λ³€κ²½ (Dirty Checking)
        user.updateInfo(updateDto.getName(), updateDto.getAge());
        
        // 3. @Transactional에 μ˜ν•΄ μžλ™ μ €μž₯
        // 4. Entityλ₯Ό Response DTO둜 λ³€ν™˜
        return UserResponseDto.from(user);
    }
}

4️⃣ DTO μ •μ˜

// Response DTO
public class UserResponseDto {
    private Long id;
    private String name;
    private String email;
    private int age;
    
    // Entity β†’ DTO λ³€ν™˜ λ©”μ„œλ“œ
    public static UserResponseDto from(User user) {
        return UserResponseDto.builder()
                .id(user.getId())
                .name(user.getName())
                .email(user.getEmail())
                .age(user.getAge())
                .build();
    }
}

πŸ”„ Entity vs DTO: μ–Έμ œ, μ™œ λ³€ν™˜ν•˜λŠ”κ°€?

🎭 각자의 μ—­ν• 

πŸ›οΈ Entity의 μ—­ν• 

  • 도메인 둜직 λ‹΄λ‹Ή: λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™κ³Ό μ œμ•½μ‚¬ν•­ 포함
  • λ°μ΄ν„°λ² μ΄μŠ€μ™€ 직접 λ§€ν•‘: ORM이 κ΄€λ¦¬ν•˜λŠ” μ˜μ†μ„± 객체
  • 생λͺ…μ£ΌκΈ° 관리: JPAκ°€ μΆ”μ ν•˜κ³  관리

πŸ“¦ DTO의 μ—­ν• 

  • 데이터 전솑 μ „μš©: 계측 κ°„ 데이터 이동
  • API μŠ€νŽ™ μ •μ˜: ν΄λΌμ΄μ–ΈνŠΈμ™€μ˜ μΈν„°νŽ˜μ΄μŠ€
  • λ³΄μ•ˆ: ν•„μš”ν•œ λ°μ΄ν„°λ§Œ λ…ΈμΆœ

βš™οΈ λ³€ν™˜ν•˜λŠ” 이유

  1. λ³΄μ•ˆ πŸ”’: Entity의 λͺ¨λ“  ν•„λ“œλ₯Ό λ…ΈμΆœν•˜λ©΄ μ•ˆ 됨
  2. μœ μ§€λ³΄μˆ˜ πŸ”§: API μŠ€νŽ™κ³Ό 도메인 λͺ¨λΈμ˜ 독립성
  3. μ„±λŠ₯ ⚑: ν•„μš”ν•œ λ°μ΄ν„°λ§Œ 전솑
  4. μœ μ—°μ„± 🀸: ν΄λΌμ΄μ–ΈνŠΈ μš”κ΅¬μ‚¬ν•­μ— λ§žλŠ” 응닡 ꡬ쑰

πŸ’‘ Service Layerμ—μ„œμ˜ 핡심 νŒ¨ν„΄

// πŸ“₯ Input: ν΄λΌμ΄μ–ΈνŠΈλ‘œλΆ€ν„° DTO λ°›κΈ°
// πŸ”„ Process: Entity둜 λ³€ν™˜ν•˜μ—¬ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 처리
// πŸ“€ Output: Entityλ₯Ό DTO둜 λ³€ν™˜ν•˜μ—¬ 응닡

πŸŽ‰ 정리

πŸ”‘ 핡심 포인트

  1. ORM의 β€œObjectβ€λŠ” OOP의 객체(Entity) λ₯Ό 의미
  2. Service LayerλŠ” DTO ↔ Entity λ³€ν™˜μ˜ 쀑심지
  3. EntityλŠ” 도메인 둜직, DTOλŠ” 데이터 μ „μ†‘μ˜ μ—­ν•  λΆ„λ‹΄
  4. ORM은 Entity와 DB ν…Œμ΄λΈ” κ°„μ˜ μžλ™ λ§€ν•‘ λ‹΄λ‹Ή

πŸ’ͺ μ‹€λ¬΄μ—μ„œ κΈ°μ–΅ν•  것

  • EntityλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ 둜직과 λ°μ΄ν„°λ² μ΄μŠ€ 맀핑을 λ‹΄λ‹Ή
  • DTOλŠ” API κ³„μΈ΅μ—μ„œλ§Œ μ‚¬μš©ν•˜μ—¬ μ™ΈλΆ€ μ˜μ‘΄μ„± 차단
  • Service Layerμ—μ„œ 두 객체 κ°„ λ³€ν™˜ λ‘œμ§μ„ λͺ…ν™•νžˆ κ΅¬ν˜„
  • @Transactionalκ³Ό Dirty Checking을 ν™œμš©ν•œ 효율적인 UPDATE 처리