꿈틀꿈틀 개발일기

20240107 / Spring 심화 복습

by jeongminy

 

❤️Github - Todolist 

https://github.com/jeongminy/todolist

 

GitHub - jeongminy/todolist

Contribute to jeongminy/todolist development by creating an account on GitHub.

github.com

 

@RequestMapping

해당 메서드가 지정된 URL 경로로 들어오는 HTTP 요청을 처리하고, 그에 따른 응답을 생성한다는 것을 의미함.

@RestController

해당 클래스가 RESTful 웹 서비스에서 컨트롤러 역할을 한다는 의미함.
RESTful 서비스는 HTTP를 통해 자원을 관리하는 웹 서비스 디자인 패턴 중 하나이다.

@RequestParam

@RequestParam(required = false, defaultValue = "ASC") order: String

 

  • @RequestParam은 HTTP 요청의 파라미터를 메서드의 매개변수에 바인딩하는 역할을 하고
  • URL의 쿼리 매개변수나 form 데이터를 받아와서 처리할 때 사용된다.
  • 간단히 말하면, 조건을 나타내는 검색어나 필터를 입력받는 상자이다.
  • 쿼리매개변수는 URL 뒤에 ?를 붙이고 key=value 형태로 추가된다.
  • 클라이언트의 요청에 따라 정렬 순서를 결정하게 되는데
  • 만약 클라이언트가 정렬 순서를 제공하지 않으면 기본값 "ASC"를 사용한다.
  • 클라이언트가 요청에 /todocards?order=DESC 와 같이, "DESC" 값을 포함하면 해당 값으로 정렬을 수행하게 된다.
  • required = false는 해당 매개변수가 필수가 아니라는 것을 나타내는 옵션이며,
  • 클라이언트가 매개변수를 제공하지 않아도 메서드는 호출될 수 있다. 만약 명시하지 않으면, 해당 매개변수는 필수로 간주되어 요청에서 생략되면 스프링이 예외를 발생시킨다.

 

ResponseEntity

스프링에서 제공하는 클래스로, HTTP 응답을 나타내는데 사용됩니다.
이 클래스는 응답의 상태 코드, 헤더, 본문(body) 등을 포함합니다.

  • 상태 코드 설정
    ResponseEntity를 통해 상태 코드를 설정할 수 있습니다.
    예를 들어, HttpStatus.OK는 200 OK를 나타냅니다.
ResponseEntity<List<TodocardResponse>>(HttpStatus.OK)

 

  • 헤더 설정
    ResponseEntity를 사용하면 응답 헤더를 설정할 수 있습니다.
ResponseEntity
    .status(HttpStatus.OK)
    .header("Custom-Header", "Header-Value")
    .body(todocardList)

 

  • 응답 본문(body) 설정
    List<TodocardResponse>가 실제 응답 본문을 나타냅니다.
ResponseEntity
    .status(HttpStatus.OK)
    .body(todocardList)

 

이렇게 구성된 ResponseEntity를 반환함으로써, 
스프링은 이 정보를 기반으로 클라이언트에게 적절한 HTTP 응답을 제공합니다.

 

ResponseEntity<꺽쇠>

꺽쇠 괄호 안에는 해당 HTTP 응답의 본문에 해당하는 데이터 타입이 들어갑니다.
즉, 어떤 종류의 데이터를 클라이언트에게 반환할 것인지를 지정합니다.

  • 단일 객체 반환.
    ResponseEntity<MyObject>​
  • 리스트나 배열 반환
    ResponseEntity<List<MyObject>> 
    // 또는
    ResponseEntity<MyObject[]>​
  • 문자열 반환
    ResponseEntity<String>​
  • 상태 코드만 반환
    ResponseEntity<Void>​

 

의존성 주입

class TodocardController( 
    private val todocardService: TodocardService
)


의존성 주입
을 통해 서비스 객체를 받아와서 해당 서비스를 활용하여 비즈니스 로직을 수행하게 하고 컴포넌트 간의 관계를 느슨하게 만듦

 

HttpStatus

Tstory 블로그 - HTTP Status code / HTTP 상태 코드 정리

MDN Web docs - HTTP 상태 코드

 

 

interface TodocardService


TodocardService 인터페이스는 주로 할 일 카드 관련 비즈니스 로직을 정의하는데 사용됩니다. 
이러한 인터페이스를 사용하는 이유는 소프트웨어 개발의 몇 가지 좋은 원칙과 패턴을 따르기 위해서입니다.
여러 이유 중 일부는 다음과 같습니다

  • 추상화와 모듈화
    TodocardService는 할 일 카드와 관련된 비즈니스 로직의 추상화를 제공합니다. 이렇게 함으로써 컨트롤러나 다른 계층에서는 구체적인 구현을 알 필요 없이 비즈니스 로직을 사용할 수 있습니다.
    인터페이스를 통해 비즈니스 로직이 어떻게 작동하는지에 대한 세부 사항은 숨겨져 있습니다.

  • 종속성 역전 원칙 (Dependency Inversion Principle)
    인터페이스를 사용하면 클라이언트 코드(컨트롤러 등)가 구체적인 구현체에 직접 의존하는 대신, 추상화된 인터페이스에 의존할 수 있습니다.
    이는 시스템을 더 유연하게 만들어 변경이나 확장이 쉬워지게 합니다.

  • 단일 책임 원칙 (Single Responsibility Principle)
    각 메서드는 특정한 기능 또는 책임을 수행합니다. 예를 들어, getAllTodocardList 메서드는 모든 할 일 카드의 목록을 가져오는 책임을 가지며, createTodocard 메서드는 새로운 할 일 카드를 생성하는 책임을 가집니다.

  • 코드의 재사용성과 유지보수 용이성
    인터페이스를 사용하면 다양한 구현체를 만들 수 있습니다. 예를 들어, 실제 데이터베이스와 상호 작용하는 구현체, 테스트를 위한 가짜 구현체 등을 만들 수 있습니다.
    이렇게 하면 코드의 재사용성이 향상되며, 유지보수가 쉬워집니다.

  • 테스트 용이성
    인터페이스를 사용하면 해당 인터페이스에 대한 모의(Mock) 구현체를 만들어 테스트할 수 있습니다. 이는 단위 테스트를 수행할 때 특히 유용합니다.
    종합하면, TodocardService 인터페이스는 코드를 더 모듈화하고 유연하게 만들어주며, 객체 지향 설계 원칙을 따르고 의존성을 관리하는 데 도움이 됩니다.

 

 

그렇다면,
의존성 주입을 하지 않는다면 어떻게 될까?

의존성 주입이 아닌 직접적인 의존성클래스가 직접 다른 클래스에 의존하는 경우입니다.
이는 유연성이 떨어지고 테스트하기 어렵다는 단점이 있습니다.
여기서는 의존성 주입이 아닌 직접적인 의존성을 보여주는 예시를 제공하겠습니다.

// 의존성 주입이 아닌 직접적인 의존성
class TodocardController {
    private val todocardService = TodocardService()

    @GetMapping("/todocards")
    fun getTodocardList(@RequestParam order: String): ResponseEntity<List<TodocardResponse>> {
        val todocardList = todocardService.getAllTodocardList(order)
        return ResponseEntity.status(HttpStatus.OK).body(todocardList)
    }
}

class TodocardService {
    // TodocardService가 직접 TodocardRepository에 의존
    private val todocardRepository = TodocardRepository()

    fun getAllTodocardList(order: String): List<TodocardResponse> {
        val todocardList = todocardRepository.getAllTodocardList(order)
        // 비즈니스 로직 수행...
        return todocardList
    }
}

class TodocardRepository {
    // TodocardRepository가 직접 데이터베이스에 의존
    fun getAllTodocardList(order: String): List<TodocardResponse> {
        // 데이터베이스 쿼리 수행...
        return listOf(TodocardResponse("Sample Todo"))
    }
}

data class TodocardResponse(val title: String)


위 코드에서 TodocardController는 TodocardService를 생성하고, 
TodocardService는 TodocardRepository를 직접 생성합니다. 
이는 의존성 주입을 사용하지 않고, 각 클래스가 직접적으로 의존하는 형태입니다.
이런 경우, 유닛 테스트를 작성하거나 클래스를 변경할 때 유연성이 부족하며, 특히 테스트하기 어렵습니다. 

 

제네릭 타입 매개변수 <T>

제네릭 타입 매개변수는 클래스나 인터페이스를 정의할 때, 
그 클래스나 인터페이스에서 사용될 타입을 나타내는 매개변수입니다. 
이를 통해 타입을 일반화하고 재사용성을 높이는 데 사용됩니다.

class Box<T>(val content: T)


여기서 <T>가 제네릭 타입 매개변수입니다. 
이 클래스는 Box라는 일반적인 박스를 나타내며, 
content라는 속성의 타입은 T로 지정됩니다. 
이제 Box 클래스를 생성할 때 어떤 타입의 값을 넣을 것인지 지정할 수 있습니다.

val stringBox = Box<String>("Hello, World!")
val intBox = Box<Int>(42)


여기서 <String><Int> 부분이 제네릭 타입 매개변수를 구체화한 것입니다.
이렇게 함으로써 content 속성의 타입이 각각 StringInt로 지정됩니다.

 

 

JpaRepository


JpaRepository는 Spring Data JPA에서 제공하는 인터페이스로, 
JPA(Java Persistence API)를 기반으로 한 데이터베이스 조작을 단순화하고 표준화하기 위해 만들어진 인터페이스입니다. 
이를 통해 개발자는 데이터베이스 조작을 위한 기본적인 CRUD(Create, Read, Update, Delete) 기능을 쉽게 사용할 수 있습니다.

주요 특징과 메서드들에 대한 간단한 설명은 다음과 같습니다:

  • CRUD 기능
    JpaRepository는 데이터베이스의 CRUD 연산을 위한 다양한 메서드를 제공합니다.
    save, findById, findAll, delete 등의 메서드를 이용하여 기본적인 데이터 조작이 가능합니다.

  • 페이징 및 정렬
    findAll 메서드를 사용할 때 페이징과 정렬을 지원합니다.
    예를 들어, findAll(Pageable pageable) 메서드를 사용하여 페이징된 결과를 얻을 수 있습니다.

  • 쿼리 메서드
    스프링 데이터 JPA는 메서드 이름으로 쿼리를 생성하는 기능을 제공합니다.
    예를 들어, findByFirstName와 같은 메서드를 정의하면 해당 이름에 맞는 쿼리를 자동으로 생성하여 실행합니다.

  • 커스텀 쿼리
    @Query 어노테이션을 사용하여 개발자가 직접 JPQL(Query DSL)이나 네이티브 쿼리를 정의할 수 있습니다. 이를 통해 더 복잡하거나 특수한 조건의 쿼리를 실행할 수 있습니다.

  • 도메인 클래스 자동 생성
    JpaRepository는 지정된 도메인 클래스의 CRUD를 담당하며,
    실제로 구현체는 Spring Data JPA에 의해 자동으로 생성됩니다.

  • 이벤트 리스너
    JPA 엔터티의 이벤트를 수신할 수 있는 리스너를 등록할 수 있습니다.
    예를 들어, @PrePersist, @PreUpdate와 같은 이벤트에 대한 메서드를 정의하여 엔터티의 저장 또는 업데이트 전에 특정 작업을 수행할 수 있습니다.

예시)

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface UserRepository : JpaRepository<User, Long> {
    fun findByUsername(username: String): User?
}


이 코드에서 UserRepository는 JpaRepository를 상속받고 있습니다. 
User는 JPA 엔터티이며, Long은 엔터티의 기본 키 타입입니다. 

findByUsername 메서드는 스프링 데이터 JPA의 쿼리 메서드 기능을 사용하여 작성된 메서드로, 
username 필드를 기준으로 사용자를 찾습니다.

 

 

JpaRepository의 CRUD기능

JpaRepository는 기본적인 CRUD(Create, Read, Update, Delete) 기능을 제공하는 Spring Data JPA의 인터페이스입니다. 
이 인터페이스를 통해 개발자는 데이터베이스 조작을 위한 다양한 메서드를 간단하게 사용할 수 있습니다. 
아래는 주요한 CRUD 기능에 대한 설명입니다

  • 저장하기 (save)
    save(S entity) 메서드는 엔터티를 데이터베이스에 저장합니다. 
    엔터티가 존재하지 않으면 새로운 레코드를 생성하고, 이미 존재하는 엔터티인 경우에는 업데이트합니다.
val user = User(username = "john_doe", email = "john@example.com")
userRepository.save(user)

 

  • 찾기 (findById, findAll)
    findById(ID id) 메서드는 주어진 ID에 해당하는 엔터티를 찾습니다.
    findAll() 메서드는 데이터베이스에 있는 모든 엔터티를 조회합니다.
val user = userRepository.findById(1L) // ID가 1인 사용자 찾기
val allUsers = userRepository.findAll() // 모든 사용자 조회

 

  • 삭제하기 (delete)
    delete(entity) 메서드는 주어진 엔터티를 데이터베이스에서 삭제합니다.
    deleteById(ID id) 메서드는 주어진 ID에 해당하는 엔터티를 삭제합니다.
val userToDelete = userRepository.findById(1L).orElse(null)
if (userToDelete != null) {
    userRepository.delete(userToDelete) // 엔터티 삭제
}

// 또는
userRepository.deleteById(1L) // ID가 1인 엔터티 삭제

 

  • 카운팅 (count)
    count() 메서드는 데이터베이스에 있는 엔터티의 총 개수를 반환합니다.
val userCount = userRepository.count() // 사용자 총 수 조회

 

  • 쿼리 메서드
    JpaRepository는 메서드 이름 규칙을 사용하여 동적인 쿼리를 생성할 수 있습니다. 예를 들어, findBy, deleteBy와 같은 접두사를 사용하면 특정 필드를 기반으로 한 검색이나 삭제 쿼리를 작성할 수 있습니다.
// username이 "john_doe"인 사용자 찾기
val user = userRepository.findByUsername("john_doe")

// username이 "john_doe"인 사용자 삭제
userRepository.deleteByUsername("john_doe")


이러한 메서드들을 통해 간단한 코드로 데이터베이스의 CRUD 작업을 수행할 수 있습니다. 
JpaRepository는 Spring Data JPA의 강력한 기능을 활용하여 개발자가 더 간편하게 데이터베이스와 상호 작용할 수 있도록 지원합니다.

 

 

JPA의 쿼리메서드 기능

JPA(Java Persistence API)의 쿼리 메서드 기능은 메서드 이름을 통해 동적으로 쿼리를 생성할 수 있는 기능을 제공합니다. 이는 매우 편리하게 데이터베이스에서 데이터를 조회하거나 조작할 수 있도록 도와주는 기능입니다. 주로 Spring Data JPA에서 사용되며, 일반적으로 Repository 인터페이스에서 쿼리 메서드를 정의하는 방식으로 활용됩니다.
쿼리 메서드의 특징은 다음과 같습니다.

  • 메서드 이름 규칙에 따른 쿼리 생성
    메서드 이름에 일정한 규칙을 따르면 Spring Data JPA가 자동으로 메서드에 맞는 쿼리를 생성해줍니다.

  • 지원하는 키워드
    findBy, readBy, getBy: 검색 조건에 해당하는 데이터를 조회.
    deleteBy, removeBy: 검색 조건에 해당하는 데이터를 삭제.

  • AND 및 OR 조건의 조합
    메서드 이름에 And, Or를 사용하여 여러 조건을 조합할 수 있습니다.

  • 정렬 및 페이징:
    메서드 이름에 OrderBy를 사용하여 정렬 조건을 지정할 수 있고, First, Top, Limit를 사용하여 페이징을 지원할 수 있습니다.

  • 네이티브 쿼리:
    @Query 어노테이션을 사용하여 메서드에 네이티브 쿼리를 직접 작성할 수 있습니다.

 

예를 들어,
TodocardRepository에서 사용되는 일부 쿼리 메서드의 예시:

interface TodocardRepository : JpaRepository<Todocard, Long> {
    
    // 메서드 이름을 통한 동적 쿼리 생성
    fun findByTitle(title: String): List<Todocard>
    
    // 여러 조건을 AND로 조합
    fun findByTitleAndAuthor(title: String, author: String): List<Todocard>
    
    // 정렬 및 페이징 지원
    fun findByAuthorOrderByCreatedTimeDesc(author: String, pageable: Pageable): List<Todocard>
    
    // 네이티브 쿼리 사용
    @Query("SELECT * FROM todocard WHERE author = :author", nativeQuery = true)
    fun findCustomByAuthor(@Param("author") author: String): List<Todocard>
}


이런 쿼리 메서드를 사용하면 개발자는 별도의 JPQL(Query DSL)이나 네이티브 쿼리를 작성하지 않고도 편리하게 데이터베이스와 상호 작용할 수 있습니다. 이를 통해 코드의 가독성이 높아지고 개발 생산성이 향상됩니다.

@Entity

@Entity 어노테이션은 JPA(Java Persistence API)에서 엔터티 클래스임을 나타내기 위해 사용됩니다. 엔터티 클래스는 데이터베이스의 테이블과 매핑되는 객체를 나타냅니다.

클래스를 엔터티로 지정하면 해당 클래스의 객체들이 데이터베이스와 매핑되어 영속성을 가지게 됩니다. JPA는 이러한 엔터티를 사용하여 데이터베이스 테이블과 상호 작용하며, 엔터티의 상태를 데이터베이스에 반영할 수 있습니다.

 

@Table(name="todocard")

이 어노테이션은 데이터베이스 테이블과 매핑되는 엔터티 클래스에 사용됩니다.
name 속성은 데이터베이스에서 사용할 테이블의 이름을 지정합니다.
위 예제에서는 "todocard"라는 테이블과 매핑됩니다.

 

@Column

@Column 어노테이션은 JPA(Java Persistence API)에서 엔터티 클래스의 필드와 데이터베이스 테이블의 컬럼 간의 매핑을 제어하기 위해 사용됩니다. 이 어노테이션을 사용하여 필드의 이름이나 타입 등을 지정할 수 있습니다.

@Column(name = "task_title", length = 50, nullable = false)
val title: String
  • name: 컬럼의 이름을 지정합니다. 위 예제에서는 "task_title"이라는 이름의 컬럼과 매핑됩니다.

  • length: 문자열 타입의 필드인 경우 컬럼의 길이를 지정할 수 있습니다. 
    위 예제에서는 최대 50자까지 저장 가능한 컬럼입니다.

  • nullable: 컬럼이 null 값 허용 여부를 나타냅니다. 
    위 예제에서는 false로 설정하여 null 값을 허용하지 않음을 나타냅니다.

 

@Id

@Id 어노테이션은 JPA(Java Persistence API)에서 엔터티 클래스의 기본 키(primary key)를 나타내기 위해 사용됩니다.
엔터티 클래스는 데이터베이스의 테이블과 매핑되며,
@Id 어노테이션을 사용하여 엔터티의 기본 키를 지정합니다.

 

 

@GeneratedValue

@GeneratedValue 어노테이션은 JPA(Java Persistence API)에서 엔터티 클래스의 기본 키를 자동으로 생성하기 위해 사용됩니다. 이 어노테이션을 사용하면 기본 키 값을 자동으로 생성하고 할당할 수 있습니다.

 

 

'📒 TIL - Today I Learned' 카테고리의 다른 글

20240109 / TIL  (0) 2024.01.09
20240108 / 뉴스피드 사전 회의  (0) 2024.01.08
20240105 / 그냥 하는 것  (0) 2024.01.05
20240104 / TIL  (0) 2024.01.04
20240103 / 하루에 조금씩 차근차근  (0) 2024.01.03

블로그의 정보

꿈틀꿈틀 개발일기

jeongminy

활동하기