LG CNS Inspire Camp AI 서비스 개발을 위한 백엔드 II (7)

오리·2026년 6월 25일

LG AM Inspire camp 5기

목록 보기
19/35
post-thumbnail

오늘 배운 것

Spring Boot Project Timelime

Annotation

객체 생성 및 라이프사이클 관리

  • @Component
    스프링이 컴포넌트 스캔으로 자동 등록하는 일반 빈(Bean) 표시. 특별한 역할 구분이 필요 없을 때 기본으로 사용.

  • @Service
    비즈니스 로직(서비스 계층) 컴포넌트임을 나타내는 @Component의 특화. 기능적으로는 빈 등록이 동일하지만, 계층/의도 표현에 유리.

  • @Controller
    MVC 웹 계층에서 요청을 처리하는 컨트롤러를 나타내는 @Component의 특화. 주로 View를 반환(또는 @ResponseBody와 함께 데이터 반환).

  • @RestController
    @Controller + @ResponseBody로, 메서드 반환값을 뷰가 아닌 HTTP 응답 바디(JSON 등) 로 바로 내려주는 REST API 컨트롤러 표시.

  • @Mapper
    MyBatis에서 SQL 매핑 인터페이스(DAO 역할) 를 스프링 빈으로 등록하기 위한 표시. 매퍼 스캔을 통해 구현체(프록시)가 자동 생성되어 주입 가능.

  • @Repository
    데이터 접근 계층(DAO/Repository) 컴포넌트임을 나타내는 @Component의 특화. DB 예외를 스프링의 DataAccessException 계열로 변환(예외 변환)해주는 역할도 가짐.

  • @ Configuration
    @Bean 정의를 모아 스프링 컨테이너에 “설정대로” 빈을 등록하는 설정 클래스 표시입니다.**

의존성 주입(DI)

  • @Autowired
    스프링이 타입 기준으로 의존성을 자동 주입. 생성자/필드/Setter에 사용 가능. 생성자 1개면 생략 가능한 경우가 많아(스프링 4.3+) 보통 생성자 주입 + Lombok 조합을 선호.
  • @RequiredArgsConstructor (Lombok)
    final 또는 @NonNull 필드만을 인자로 받는 생성자를 자동 생성. 스프링에서 생성자 주입과 함께 쓰면 보일러플레이트를 줄임.
  • @Inject (JSR-330)
    @Autowired와 유사한 표준 DI 어노테이션. 스프링에서도 지원. 보통 타입 기준으로 주입되며, 선택적 의존성/필수 여부 등 세부 옵션은 스프링 어노테이션이 더 풍부한 편.
  • @Resource (JSR-250)
    기본적으로 이름(name) 우선으로 주입(없으면 타입으로). @Resource(name="beanName")처럼 빈 이름을 지정해 주입 대상을 명확히 할 때 유용.
  • @Qualifier
    같은 타입의 빈이 여러 개일 때, @Autowired와 함께 주입할 빈을 식별하는 용도. 예: @Qualifier("mainDataSource"). (참고: 노트에 @Qulifier로 적혀 있는데 올바른 철자는 @Qualifier.)

요청 매핑(Mapping) 어노테이션 설명

  • @RequestMapping (class level)
    클래스 전체에 공통 URL prefix(기본 경로) 를 지정. 예: @RequestMapping("/api/users")를 클래스에 붙이면, 아래 메서드 매핑은 /api/users/... 아래로 묶임.
  • @RequestMapping (method level)
    특정 메서드가 처리할 요청 경로 + HTTP 메서드 를 매핑. method 속성으로 GET/POST/... 등을 지정할 수 있음.
    - 예: @RequestMapping(value="/{id}", method=RequestMethod.GET)
  • @GetMapping / @PostMapping / @PutMapping / @PatchMapping / @DeleteMapping
    @RequestMapping(method=...)의 축약형(sugar). 가독성이 좋아서 실무에서는 보통 이 매핑들을 주로 사용.
    - 예: @GetMapping("/{id}"), @PostMapping("")

같이 알아두면 좋은 포인트

  • 경로 변수(Path Variable): URL의 {id} 같은 값을 파라미터로 받기 위해 @PathVariable을 사용. 예: getUser(@PathVariable Long id)
  • 쿼리 파라미터(Query Parameter): ?page=1 같은 값을 받기 위해 @RequestParam을 사용.
  • (참고) 스프링은 @RequestMapping을 포함한 매핑 어노테이션을 분석해 어떤 컨트롤러 메서드가 어떤 요청을 처리할지 DispatcherServlet의 라우팅(HandlerMapping) 단계에서 결정함.

@GetMapping, @PostMapping은 메서드 레벨 @RequestMapping을 더 명확하고 짧게 쓴 “전용(축약) 매핑 애노테이션”입니다. 즉, “메서드 레벨 요청 매핑”이라는 큰 범주 안에 들어가고, 내부적으로는 결국 @RequestMapping으로 해석됩니다.


FrontEnd - BackEnd 연동 프로젝트 !

1. application.yaml 설정

지난 수업처럼 서버 포트와 XML 매퍼 경로 챙겨놓기

2. 폴더 구조 생성

📂 패키지 구조 구조 핵심 해설

  1. ctrl (Controller)
    역할: 브라우저나 리액트(FE)의 URL 요청을 가장 먼저 맞이하는 문지기
  • UserController.class: 회원가입 주소(/user/signup)나 로그인 주소(/user/signin)로 들어온 사용자의 요청을 접수하고, 적절한 데이터 형식으로 변환하여 서비스 레이어로 넘겨줌
  1. domain
    역할: 데이터베이스 테이블 구조나, 시스템 내에서 주고받는 데이터의 알맹이(규격)를 정의하는 곳
  • dto (Data Transfer Object):

    • UserRequestDTO.class: 사용자가 로그인할 때 입력한 아이디/비밀번호 등 받아오는 데이터의 바구니
    • UserResponseDTO.class: 로그인이 성공한 후 리액트 화면에 넘겨줄 회원의 이름, 토큰 정보 등 보내줄 데이터의 바구니
  • vo (Value Object): 데이터베이스 테이블과 1:1로 매핑되는 순수한 회원 엔티티(Entity) 데이터 모델 구조를 담음

  1. repository
    역할: 실제 데이터베이스(MariaDB 등)에 접근해 SQL을 날리는 DB 전담 일꾼
  • UserMapper.class: MyBatis 인터페이스
    자바 코드와 실제 SQL 문이 담긴 XML 파일(UserMapper.xml)을 중간에서 부드럽게 연결해 주는 가교 역할
  1. service
    역할: 비즈니스 로직(핵심 기능)을 수행하는 컨트롤 타워
  • UserService.class: "비밀번호가 데이터베이스에 있는 것과 일치하는가?", "아이디가 중복되었는가?", "로그인이 성공했으니 JWT 토큰을 발행해 줄까?" 같은 실제 프로그램의 핵심 알고리즘과 판단 연산이 이 안에서 일어남

🔄 한눈에 보는 데이터 흐름 (Mechanism)

[1. 웹 브라우저 / 리액트]
↓ (로그인 요청: 이메일, 패스워드)
[2. UserController] <-- UserRequestDTO 바구니에 데이터를 담아 접수!
↓
[3. UserService] <-- 비밀번호 암호화 및 비즈니스 로직 검증 판단!
↓
[4. UserMapper] <-- 데이터베이스에 회원이 있는지 쿼리 요청!
↓
[5. MariaDB (DB)]

3. FrontEnd - BackEnd 연동하기

CorsConfig 부분
프론트엔드에서 보낸 로그인 데이터 (이메일, 패스워드)를 백엔드 MyBatis DB 조회 쿼리에 안전하게 매핑해주는 파라미터 규격 역할을 함.

react 회원가입 부분과
react 부분
Controller에서 연결해주면




회원가입 정보가 DB로 들어온다!

+) XML 연동은 지난 시간에 했던 과정을 동일하게 거치면 된다.

4. Swagger index


이런 페이지를 만들어서 API 명세서 처럼 생각하면 될 듯 하다.

package com.inspire.lgcns_mybatis.features.user.ctrl;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.inspire.lgcns_mybatis.features.user.domain.dto.UserRequestDTO;
import com.inspire.lgcns_mybatis.features.user.domain.dto.UserResponseDTO;
import com.inspire.lgcns_mybatis.features.user.service.UserService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;


/*
Controller 의 역할
- 파라미터를 DTO로 바인딩 해서 데이터의 유효성을 체크
- 서비스 레이어의 의존성을 가짐
- ResponseEntity 통해서 데이터 및 상태 코드를 전달

 */
@Tag(name = "User API", description = "사용자 생성 및 로그인 관련 API 명세서")
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;

    @Operation(
        summary = "회원가입", 
        tags = {"User API"},
        responses = {
            @ApiResponse(responseCode = "201", description = "가입성공"),
            @ApiResponse(responseCode = "500", description = "가입실패")
        }
    )
    @PostMapping("/signUp")
    public ResponseEntity<Void> signUp(
        @io.swagger.v3.oas.annotations.parameters.RequestBody(
            description = "사용자 가입정보를 담는 DTO",
            required    = true,
            content     = @Content(
            schema  = @Schema(implementation = UserRequestDTO.class)
        ))
        @RequestBody UserRequestDTO request) {
        //TODO: process POST request
        System.out.println(">>> debug user controller signUp");
        System.out.println(" >>> debug params :" + request);

        int insertFlag = userService.signUp(request);
        if (insertFlag != 0){
            return ResponseEntity.status(HttpStatus.CREATED).build();
        } else {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
        
    }

    @Operation(
        summary = "로그인", 
        description = "사용자 로그인(email,password)",
        tags = {"User API"},
        responses = {
            @ApiResponse(responseCode = "200", description = "로그인성공"),
            @ApiResponse(responseCode = "400", description = "로그인실패")
        }
    )
    @PostMapping("/signIn")
    /*
    - json web Token
    - 인증 : 너 누귀야 ? 인가 : 너에게 권한을 줄게
     */
    public ResponseEntity<UserResponseDTO> signIn(@RequestBody UserRequestDTO request) {
        System.out.println(">>> debug user controller signIn");
        System.out.println(" >>> debug params :" + request);

        Map<String,Object> map =  userService.signIn(request);
        
        
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", (String)(map.get("at")));
        headers.add("Refresh-Token", (String)(map.get("rt")));
        headers.add("Access-Control-Expose-Headers", "Authorization, Refresh-Token");
        

        return ResponseEntity
                .status(HttpStatus.OK)
                .headers(headers)
                .body((UserResponseDTO)(map.get("data")));

    }
    

    
}

여기서


빨간박스 내용들이 Swagger Code다

다음 계획

사후평가가 얼마 안 남았다...
진짜 달려야해 ㅜㅜ

profile
IT 전공자의 발버둥

0개의 댓글