π Spring Boot Service Layerμ ORM μλ²½ μ΄ν΄νκΈ°
μ μ΄ λ¬Έμλ₯Ό μμ±νλ? π€
Java Spring Boot λ°±μλ νλ‘μ νΈμμ Service Layerμ CRUD λ©μλλ₯Ό ꡬννλ©΄μ, Response DTO μμ±κ³Ό λ°ν κ³Όμ μμ ORMκ³Ό μλΉμ€ λ‘μ§μ ν΅μ¬μ μ λλ‘ μ΄ν΄νμ§ λͺ»ν΄ μ 리ν νμ΅ λ ΈνΈμ λλ€.
π― ν΅μ¬ λͺ©ν
Service Layerμμ Entity β DTO λ³ν κ³Όμ κ³Ό ORMμ μν μ μλ²½ν μ΄ν΄νμ!
π λͺ©μ°¨
- ORMμ μ΄ν΄νκΈ° μν κ°μ²΄(Object) κ°λ μ 리
- ORMμ΄λ 무μμΈκ°?
- Spring Boot Service Layerμμμ μ€μ μ μ©
- 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 μ€ν μ μ: ν΄λΌμ΄μΈνΈμμ μΈν°νμ΄μ€
- 보μ: νμν λ°μ΄ν°λ§ λ ΈμΆ
βοΈ λ³ννλ μ΄μ
- 보μ π: Entityμ λͺ¨λ νλλ₯Ό λ ΈμΆνλ©΄ μ λ¨
- μ μ§λ³΄μ π§: API μ€νκ³Ό λλ©μΈ λͺ¨λΈμ λ 립μ±
- μ±λ₯ β‘: νμν λ°μ΄ν°λ§ μ μ‘
- μ μ°μ± π€Έ: ν΄λΌμ΄μΈνΈ μꡬμ¬νμ λ§λ μλ΅ κ΅¬μ‘°
π‘ Service Layerμμμ ν΅μ¬ ν¨ν΄
// π₯ Input: ν΄λΌμ΄μΈνΈλ‘λΆν° DTO λ°κΈ°
// π Process: Entityλ‘ λ³ννμ¬ λΉμ¦λμ€ λ‘μ§ μ²λ¦¬
// π€ Output: Entityλ₯Ό DTOλ‘ λ³ννμ¬ μλ΅
π μ 리
π ν΅μ¬ ν¬μΈνΈ
- ORMμ βObjectβλ OOPμ κ°μ²΄(Entity) λ₯Ό μλ―Έ
- Service Layerλ DTO β Entity λ³νμ μ€μ¬μ§
- Entityλ λλ©μΈ λ‘μ§, DTOλ λ°μ΄ν° μ μ‘μ μν λΆλ΄
- ORMμ Entityμ DB ν μ΄λΈ κ°μ μλ λ§€ν λ΄λΉ
πͺ μ€λ¬΄μμ κΈ°μ΅ν κ²
- Entityλ λΉμ¦λμ€ λ‘μ§κ³Ό λ°μ΄ν°λ² μ΄μ€ λ§€νμ λ΄λΉ
- DTOλ API κ³μΈ΅μμλ§ μ¬μ©νμ¬ μΈλΆ μμ‘΄μ± μ°¨λ¨
- Service Layerμμ λ κ°μ²΄ κ° λ³ν λ‘μ§μ λͺ νν ꡬν
- @Transactionalκ³Ό Dirty Checkingμ νμ©ν ν¨μ¨μ μΈ UPDATE μ²λ¦¬