
백엔드 개발자를 준비하며 생기는 의문들을 정리한 포스트입니다.
Annotation은 사전적 의미로 주석이라는 뜻이지만
자바에서는Annotation (@)은 코드 사이에 특별한 의미, 기능을 수행하도록 하는 기술입니다.그렇다는 말은 스프링 프레임워크에서 사용하는 annotation은 어플리케이션의 구성,
빈 관리, 의존성 주입 등을 간편하게 처리할 수 있도록 도와줍니다.
즉 프로그램 코드의 일부가 아닌 프로그램에 관한 데이터를 제공하고
코드에 정보를 추가하는 정형화된 방법입니다.
Annotation을 사용하면 코드가 깔끔해지고 재사용이 가능합니다.
- Annotation은
@를 사용해서 정의합니다.- 클래스(class)위에 어노테이션을 배치합니다.
- 코드가 실행되는 중에 Reflection을 이용하여 기능을 실시합니다.
다른 파일에서 가지고 오고 싶은 특정 메소드나 클래스에
@를 붙이면 그 기능이 확장되어 사용할 수 있습니다.
스프링 부트 애플리케이션의 진입점을 정의하는 어노테이션 입니다.
스프링 부트의 애플리케이션을 실행할 때 한번쯤 봤을 것 같습니다.

스프링 부트를 자동으로 실행시켜주는 필수 어노테이션입니다.
Spring Bean으로 등록할 클래스를 지정합니다.
@Component
class MyService {
fun getMessage() = "Hello, Spring Boot!"
}
스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트이며
즉 스프링 컨테이너가 관리하는 자바 객체를 뜻합니다.
Spring Bean에 관한 글은 한번 더 다루도록 하겠습니다.
Service Class에서 쓰이는 어노테이션으로
비즈니스 로직을 수행하는 Class 라는 것을 나타내는 용도입니다.
@Service
class UserService {
fun getUser() = "Hood"
}
데이터 접근 계층(DAO) Class에서 사용되는 어노테이션입니다.
DB에 접근하는 로직을 처리하는 계층에서 사용됩니다.
@Repository
class UserRepository {
fun findUserById(id: Long): String = "User$id"
}
스프링 컨테이너에서 의존성을 자동으로 주입받습니다.
필드, setter 메소드, 생성자에 사용하며 Type에 따라서 알아서 Bean을 주입해주는 역할을 합니다.
객체에 대한 의존성을 주입시킵니다.
@Autowired를 사용하면 스프링이 자동적으로 값을 할당합니다.
RESTful 웹 서비스 컨트롤러를 정의합니다.
Spring에서 Controller 중 View로 응답하지 않는 Controller를 의미하며
메소드의 반환 결과를 Json 형태로 반환합니다.@RestController가 적혀있는 컨트롤러의 메소드는 HttpResponse로 바로 응답이 가능합니다.
@ResponseBody의 역할을 자동적으로 해주는 어노테이션입니다.
- @Controller는 API와 View를 동시에 사용하는 경우에 사용
대신 API 서비스로 사용하는 경우는 @ResponseBody를 사용하여 객체를 반환합니다.- @RestController는 View가 필요없는 API만 지원하는 서비스에서 사용
@RequestMapping 메소드가 기본적으로 @ResponseBody 의 의미도 포함되있습니다.@RestController = @Controller + @ResponseBody
특정 URL과 메소드를 매핑합니다.
어떤 URL이 어떤 메소드가 처리할 지 매핑해주는 어노테이션입니다.
요청을 받는 형식에 따라 GET/POST/PUT/PATCH/DELETE를 정의합니다.
(정의하지 않으면 기본적으로 GET으로 설정됩니다.)
@RestController
@RequestMapping("/api")
class ApiController {
@GetMapping("/greet")
fun greet() = "Greetings!"
}
@RequestMapping(Method = RequestMethod.GET/POST/PUT/PATCH/DELETE)
위 와 같은 의미이며 사용하는 요청 방식에 따라 사용됩니다.
@RestController
class ProductController {
@PostMapping("/product")
fun createProduct(@RequestBody product: String) = "Product created: $product"
@GetMapping("/product/{id}")
fun getProduct(@PathVariable id: Long) = "Product ID: $id"
}
URL의 쿼리 파라미터 값을 매핑합니다.
기본적으로 필수 파라미터이며 값이 없을 경우 오류가 발생합니다.
defaultValue를 사용해 기본 값을 설정할 수 있습니다.
@RestController
@RequestMapping("/api")
class RequestParamController {
@GetMapping("/greet")
fun greetUser(@RequestParam name: String) = "Hello, $name!"
@GetMapping("/sum")
fun sum(@RequestParam num1: Int, @RequestParam num2: Int) = "Sum: ${num1 + num2}"
@GetMapping("/optional")
fun optionalParam(@RequestParam(defaultValue = "Guest") name: String) = "Hello, $name!"
}
요청 예시 :
/api/greet?name=John→ "Hello, John!"/api/sum?num1=5&num2=3→ "Sum: 8"/api/optional→ `"Hello, Guest!"
URL 경로의 일부 변수를 매핑합니다.
@RequestParam과 다르게 경로 자체에서 값을 가져옵니다.
URL의 일부를 변수로 사용할 때 사용합니다.
@RestController
@RequestMapping("/users")
class PathVariableController {
@GetMapping("/{id}")
fun getUserById(@PathVariable id: Long) = "User ID: $id"
@GetMapping("/{userId}/orders/{orderId}")
fun getOrderDetails(
@PathVariable userId: Long,
@PathVariable orderId: Long
) = "User $userId's Order $orderId"
}
요청 예시 :
/users/123→ "User ID: 123"/users/123/orders/456→ "User 123's Order 456"
HTTP 요청 본문을 Java/Kotlin 객체로 매핑합니다.
주로 POST, PUT 요청에서 사용되며
JSON 데이터와 DTO를 직접 메핑합니다.
data class UserDto(
val name: String,
val age: Int
)
@RestController
@RequestMapping("/users")
class RequestBodyController {
@PostMapping
fun createUser(@RequestBody userDto: UserDto): String {
return "Created user: ${userDto.name}, Age: ${userDto.age}"
}
}
POST /users
Content-Type: application/json
{
"name": "DH",
"age": 25
}
-> "Created user: DH, Age: 25"
멀티파트 데이터(파일 업로드)와 함께 JSON 데이터 처리에 사용됩니다.
파일 업로드와 다른 데이터(ex. JSON)를 동시에 처리할 때 사용하빈다.
@RequestParam과 비슷하지만,multipart/from-data에서 사용됩니다.
data class ProductDto(
val name: String,
val price: Double
)
@RestController
@RequestMapping("/products")
class RequestPartController {
@PostMapping("/upload")
fun uploadProduct(
@RequestPart("product") productDto: ProductDto,
@RequestPart("file") file: MultipartFile
): String {
return "Uploaded ${productDto.name} with file: ${file.originalFilename}"
}
}
Key: "product" → {"name": "Laptop", "price": 1500.0}
Key: "file" → laptop.jpg
→ "Uploaded Laptop with file: laptop.jpg"
HTTP 요청 헤더를 매핑합니다.
API 토큰이나 사용자 정보, 언어 설정 등을 가져올 때 사용합니다.
@RestController
@RequestMapping("/headers")
class RequestHeaderController {
@GetMapping
fun getHeaderInfo(
@RequestHeader("User-Agent") userAgent: String,
@RequestHeader("Authorization", required = false) authHeader: String?
): String {
return "User-Agent: $userAgent, Auth: ${authHeader ?: "No Token"}"
}
}
GET /headers
User-Agent: Mozilla/5.0
Authorization: Bearer abc123
트랜젝션을 관리하고 데이터베이스 변경 시 롤백을 지원합니다.
DB 트랜잭션을 설정하고 싶은 메소드에 어노테이션만 적용하면
메소드 내부에서 일어나는 DB 로직이 전부 성공하게 되거나
DB 접근 중 하나라도 실패하면 다시 롤백을 할 수 있게 해주는 어노테이션으로
DB 데이터를 등록/수정/삭제하는 서비스 로직에 필수적이라고 볼 수 있습니다.
@Service
class BankService(val accountRepository: AccountRepository) {
@Transactional
fun transferMoney(fromAccount: String, toAccount: String, amount: Double) {
accountRepository.debit(fromAccount, amount)
accountRepository.credit(toAccount, amount)
}
}
특정 예외가 발생했을 때 예외를 처리하는 메서드를 지정합니다.
예외를 캐치해서 처리합니다.
@ExceptionHandler(예외클래스명::class)를 붙여서 사용합니다.
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException::class)
fun handleRuntimeException(e: RuntimeException) = "Error: ${e.message}"
}
데이터 유효성 검사에 사용됩니다.
data class User(
@field:NotBlank val name: String,
@field:Email val email: String
)
@PostMapping("/user")
fun createUser(@Valid @RequestBody user: User) = "User Created: ${user.name}"
JPA란? Java Persistence API의 약자로
자바 객체와 관계형 데이터베이스간의 매핑(ORM)을 제공하는 인터페이스 입니다.ORM이란? 객체와 데이터베이스 테이블을 매핑하는 기술
JPA를 통해 개발자는 SQL 쿼리를 직접 작성하지 않고 메서드 호출만으로 조작 가능합니다.
데이터베이스의 테이블과 매핑되는 클래스이며
@Entity를 사용하는 클래스는 기본 키(@Id)가 필요합니다.
@Entity
data class User(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
val name: String,
val email: String
)
@Entity: User 클래스를 데이터베이스 테이블과 매핑.
@Id: 기본 키 지정.
@GeneratedValue: 기본 키 자동 생성 전략.
엔티티와 매핑될 데이터베이스 테이블 이름을 지정합니다.
@Entity
@Table(name = "users_table") // 테이블 이름 지정
data class User(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
val name: String,
val email: String
)
@Table(name = "users_table"): 데이터베이스의 "users_table"과 매핑.
필드를 데이터베이스의 컬럼과 매핑합니다.
nullable,length등 제약 조건 설정이 가능합니다.
@Column(name = "user_name", nullable = false, length = 50)
val name: String
@Column(name = "user_name"): name 필드를 "user_name" 컬럼과 매핑.
nullable = false: null 값을 허용하지 않음.
length = 50: 최대 길이 50으로 제한.
기본 키를 설정하고, 기본 키의 자동 생성 지정합니다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0
@Id: 기본 키 지정.
@GeneratedValue: 기본 키 자동 생성 지정
IDENTITY: 데이터베이스가 자동으로 생성 (MySQL AUTO_INCREMENT와 유사).
SEQUENCE: 시퀀스 객체를 사용 (주로 Oracle).
TABLE: 키 생성 전용 테이블 사용.
AUTO: 데이터베이스에 맞게 자동 선택.
자바의
enum타입을 컬럼에 매핑합니다.
enum class Role {
USER, ADMIN
}
@Entity
data class User(
@Id @GeneratedValue
val id: Long = 0,
@Enumerated(EnumType.STRING)
val role: Role
)
@Enumerated(EnumType.STRING) → USER나 ADMIN 문자열 그대로 저장.
한 엔티티가 하나의 엔티티와 매핑됩니다.
@Entity
data class User(
@Id @GeneratedValue
val id: Long = 0,
@OneToOne(cascade = [CascadeType.ALL])
@JoinColumn(name = "profile_id")
val profile: Profile
)
@Entity
data class Profile(
@Id @GeneratedValue
val id: Long = 0,
val bio: String
)
@OneToOne: User와 Profile이 일대일 관계.
@JoinColumn(name = "profile_id"): 외래 키를 profile_id로 설정.
cascade = CascadeType.ALL: 영속성 전이 (User 저장 시 Profile도 함께 저장).
하나의 엔티티가 여러 엔티티를 참조합니다.
@Entity
data class User(
@Id @GeneratedValue
val id: Long = 0,
@OneToMany(mappedBy = "user", cascade = [CascadeType.ALL])
val posts: List<Post> = mutableListOf()
)
@Entity
data class Post(
@Id @GeneratedValue
val id: Long = 0,
@ManyToOne
@JoinColumn(name = "user_id")
val user: User
)
@OneToMany(mappedBy = "user"): 주인 아닌 쪽에서 참조.
@ManyToOne: 다 쪽에서 외래 키를 가지고 있음.
cascade = CascadeType.ALL: 연관 엔티티의 동시 저장/삭제 가능.
mappedBy를 사용하지 않으면 조인 테이블이 생성됨!
여러 엔티티가 하나의 엔티티를 참조
@Entity
data class Post(
@Id @GeneratedValue
val id: Long = 0,
val content: String,
@ManyToOne
@JoinColumn(name = "author_id")
val author: User
)
@ManyToOne: 여러 게시글(Post)이 하나의 User를 참조.
@JoinColumn(name = "author_id"): author_id를 외래 키로 지정.
여러 엔티티가 서로 여러 개를 참조
@Entity
data class Student(
@Id @GeneratedValue
val id: Long = 0,
val name: String,
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = [JoinColumn(name = "student_id")],
inverseJoinColumns = [JoinColumn(name = "course_id")]
)
val courses: MutableList<Course> = mutableListOf()
)
@Entity
data class Course(
@Id @GeneratedValue
val id: Long = 0,
val title: String
)
@ManyToMany: 학생과 강의가 다대다 관계.
@JoinTable: 연결 테이블을 명시.
joinColumns: student_id를 외래 키로 설정.
inverseJoinColumns: course_id를 외래 키로 설정.
기본값은
@ManyToOne,@OneToOne이 있으며 연관된 데이터를 즉시 로딩합니다.
@ManyToOne(fetch = FetchType.EAGER)
기본 값은
@OneToMany,@ManyToMany가 있으며
연관된 데이터를 실제로 사용할 때만 로딩합니다.
@OneToMany(fetch = FetchType.LAZY)
부모 엔티티의 작업을 자식 엔티티에도 전이합니다.
@OneToMany(cascade = [CascadeType.ALL])
val posts: List<Post> = mutableListOf()
ALL: 모두 전이.
PERSIST: 저장 전이.
REMOVE: 삭제 전이.
MERGE: 병합 전이.
Annotation이란?
프로그램에게 추가적인 정보를 제공해주는 메타데이터이며
소프트웨어 개발 툴이 빌드나 배치시 코드를 자동적으로 생성할 수 있도록 정보를 제공합니다.
즉, 다른 파일에서 가지고 오고 싶은 특정 메소드나 클래스에
@를 붙이면 그 기능이 확장되어 사용할 수 있습니다.