백엔드 코틀린 개발하기 2주차 모르는 개념 정리

백엔드 코틀린 개발하기 2주차 모르는 개념 정리

Domain 개발하기

데이터 초기화

@Component // 스프링 빈으로 등록
  • 어떤 클래스를 빈으로 등록할지 알려주는 어노테이션

📌 @Component란?

@ComponentSpring Framework에서 빈(Bean)으로 등록하기 위한 기본적인 애너테이션입니다.
즉, Spring이 해당 클래스를 자동으로 스프링 컨테이너(IOC 컨테이너)에 등록하도록 해줍니다.

🚀 @Component 사용 방법

@Component
class MyService {
    fun doSomething() {
        println("MyService is working!")
    }
}
  • @Component를 사용하면 MyService 클래스가 Spring Bean으로 등록됨.

  • 다른 곳에서 @Autowired 또는 생성자 주입을 통해 사용할 수 있음.

@Service
class SomeClass(private val myService: MyService) {
    fun execute() {
        myService.doSomething()
    }
}
  • SomeClass에서 MyService를 자동으로 주입받아 사용 가능

     

     

    🎯 @Component vs 기타 빈 애너테이션

Spring에서는 @Component 외에도 특정한 목적에 맞는 다른 애너테이션들이 있습니다.

애너테이션 설명 @Component 가장 기본적인 빈 등록 애너테이션 @Service 비즈니스 로직을 처리하는 클래스에 사용 @Repository 데이터베이스 작업(DAO, Repository)에 사용 @Controller Spring MVC 컨트롤러(웹 요청 처리)에 사용

💡 실제 동작은 동일하지만, 역할에 맞게 애너테이션을 사용하는 것이 가독성과 유지보수성을 높이는 데 도움이 됩니다.

📌 @Component를 어디서 쓰면 좋을까?

사용하면 좋은 경우

  • 특정한 역할이 없는 일반적인 Bean을 만들 때 (Utility Class, Helper, Config)

  • 다른 서비스, 레포지토리와는 다르게 특별한 역할이 없는 경우

다른 애너테이션을 쓰는 것이 좋은 경우

  • 비즈니스 로직을 처리하는 경우 → @Service

  • 데이터 액세스 관련 클래스 → @Repository

  • 웹 요청을 처리하는 컨트롤러@Controller

🔥 결론

  • @ComponentSpring Bean을 등록하는 가장 기본적인 애너테이션.

  • 역할이 명확한 경우 @Service, @Repository, @Controller를 사용하는 것이 더 가독성이 좋음.

  • 주로 일반적인 유틸리티 클래스나 특정한 계층에 속하지 않는 클래스에서 사용됨. 🚀


    📌 @PostConstruct란?

@PostConstructSpring에서 빈(Bean)이 생성된 후 자동으로 실행되는 메서드를 지정하는 애너테이션입니다.

즉, 스프링 컨테이너가 해당 빈을 생성하고 의존성 주입(DI)이 완료된 후 한 번 실행되는 초기화 메서드를 정의할 때 사용된다.

🚀 @PostConstruct 기본 사용 예시

@Component
class MyService {

    @PostConstruct
    fun initializeData() {
        println("MyService 초기화 중...")
        // 초기 데이터 설정 로직
    }
}

실행 흐름:

  1. MyService스프링 컨테이너에 의해 빈(Bean)으로 생성됨.

  2. 의존성 주입(DI)이 완료됨.

  3. @PostConstruct가 붙은 initializeData() 메서드가 자동 실행됨.

💡 @PostConstruct의 주요 특징

  1. Spring Bean이 생성된 후, 단 한 번만 실행됨.

  2. 의존성 주입이 완료된 후 실행되므로, 다른 빈을 사용할 수 있음.

  3. 애플리케이션이 실행될 때 한 번만 초기화해야 하는 작업에 적합.

🎯 @PostConstruct가 유용한 경우

사용 예시 설명 초기 데이터 설정 DB에서 기본 데이터를 불러오거나, 캐시를 로딩할 때 외부 API 연동 애플리케이션 시작 시 특정 API와 통신해야 할 때 리소스 초기화 특정 설정 파일을 읽거나, 초기 환경을 세팅할 때

📌 데이터 초기화 예시

@Service
class DataService(private val repository: MyRepository) {

    @PostConstruct
    fun initializeDatabase() {
        if (repository.count() == 0L) {
            repository.saveAll(listOf(MyEntity("데이터1"), MyEntity("데이터2")))
            println("초기 데이터 삽입 완료")
        }
    }
}

데이터베이스에 초기 데이터를 넣어야 할 때 유용함.

주의할 점

비동기 작업 (@Async)과 함께 사용하면 안 됨

  • @PostConstruct빈 초기화 이후 즉시 실행되므로 비동기 메서드와 충돌 가능.

  • 대안: ApplicationRunner 또는 CommandLineRunner를 활용.

@Bean
fun initRunner() = ApplicationRunner {
    println("애플리케이션 실행 후 초기화 코드 실행")
}

🔥 결론

  • @PostConstructSpring Bean이 생성된 후 단 한 번 실행되는 초기화 메서드.

  • 초기 데이터 설정, API 호출, 설정값 초기화 등에 유용.

  • 비동기 작업과 함께 사용하면 예상치 못한 문제가 발생할 수 있음.
    이럴 때는 ApplicationRunner를 고려하는 것이 좋음. 🚀


    📌 achievementRepository.saveAll(achievements) 데이터가 없는데도 되는 이유

Spring Data JPA의 saveAll() 메서드는 비어있는 리스트(List<T>)를 전달해도 오류 없이 실행되도록 설계되어 있다.

즉, achievements 리스트가 비어있다면 아무런 데이터도 저장되지 않고, 그냥 정상적으로 메서드가 종료된다.

🚀 saveAll() 동작 방식

achievementRepository.saveAll(emptyList()) // 데이터가 없어도 실행됨

내부적으로 실행되는 로직

  • 비어있는 리스트(emptyList<T>())가 들어오면, 아무 동작 없이 바로 반환.

  • 데이터가 있으면 각각의 엔티티를 save() 메서드를 이용해 Batch Insert 또는 개별 저장 수행.

  • 트랜잭션이 있으면 한 번에 처리되고, 없으면 개별 실행됨.

🎯 saveAll()이 실행될 때 두 가지 경우

1⃣ 데이터가 있을 때

val achievements = listOf(
    Achievement(name = "Level 1", score = 10),
    Achievement(name = "Level 2", score = 20)
)
achievementRepository.saveAll(achievements) // DB에 저장됨
  • saveAll()리스트에 있는 엔티티들을 한꺼번에 저장함.

2⃣ 데이터가 없을 때

val emptyList = emptyList<Achievement>()
achievementRepository.saveAll(emptyList) // 아무 일도 일어나지 않음
  • saveAll()빈 리스트가 들어오면 아무런 작업도 하지 않고 그냥 종료됨.

  • SQL 쿼리가 실행되지 않으며, 오류도 발생하지 않음.


🔥 결론

saveAll(emptyList())는 아무런 데이터도 저장하지 않고 그냥 종료되도록 설계됨.
데이터가 없을 경우에도 오류 없이 정상 동작함.
Spring Data JPA는 이런 경우를 고려하여 예외를 던지지 않음.

📌 즉, 데이터가 없어도 saveAll()이 실행되지만, 실제로 DB에는 아무런 변경이 일어나지 않는 것! 🚀

 

Kotlin에서 fun의 의미

Kotlin에서 fun 키워드는 함수를 정의할 때 사용되는 키워드
Java의 public List<Skill> findAllByIsActive(boolean isActive)와 같은 역할을 한다.

🚀 fun findAllByIsActive(isActive: Boolean): List<Skill>

// select * from skill where is_active = :isActive
fun findAllByIsActive(isActive: Boolean): List<Skill>

설명

  • findAllByIsActive(isActive: Boolean):

    • isActive 값이 true 또는 false인 데이터를 조회하는 메서드.

    • SQL 변환: SELECT * FROM skill WHERE is_active = :isActive

    • 반환 타입: List<Skill> → 여러 개의 Skill 객체 리스트를 반환.

🔹 사용 예시

val activeSkills: List<Skill> = skillRepository.findAllByIsActive(true)
println(activeSkills) // 활성화된 스킬 목록 출력

🚀 fun findByNameIgnoreCaseAndType(name: String, type: SkillType): Optional<Skill>

// select * from skill where lower(name) = lower(:name) and type = :type
fun findByNameIgnoreCaseAndType(name: String, type: SkillType): Optional<Skill>

설명

  • findByNameIgnoreCaseAndType(name: String, type: SkillType):

    • 이름(name) 대소문자 구분 없이 검색하고,

    • 타입(type)이 일치하는 데이터를 조회.

    • SQL 변환: SELECT * FROM skill WHERE lower(name) = lower(:name) AND type = :type

    • 반환 타입: Optional<Skill> → 조회된 값이 있을 수도 있고, 없을 수도 있음.

🔹 사용 예시

val skill: Optional<Skill> = skillRepository.findByNameIgnoreCaseAndType("java", SkillType.PROGRAMMING)

if (skill.isPresent) {
    println("Skill found: ${skill.get()}")
} else {
    println("Skill not found")
}

🔥 결론

  1. Kotlin에서 fun은 함수 선언 키워드.

  2. findAllByIsActive(isActive: Boolean): 활성화된(is_active) 데이터를 리스트로 반환.

  3. findByNameIgnoreCaseAndType(name, type): 대소문자 구분 없이 특정 이름과 타입을 가진 데이터를 조회.

  4. Spring Data JPA는 메서드 이름을 기반으로 자동으로 SQL을 생성하여 실행함. 🚀

 

회고

사실 왕복 3시간 거리 회사를 다니고 지금 커피챗, 친구들 만나기, 스터디 2개까지 해서 솔직히 버거워서 밀렸음 그래도 일단 과제를 내고자 하는 의지로 내는 중...

댓글을 작성해보세요.

채널톡 아이콘