카테고리 없음

Spring boot MVC RESTful CRUD

ALEPH.GEM 2026. 7. 16. 21:56
728x90

 

@RequestMapping

@RequestMapping은 Spring MVC의 어노테이션이며, 특정 URL 요청을 어떤 컨트롤러의 메서드가 처리할지 연결(mapping)해주는 역할을 합니다.

예를 들어 @RequestMapping(value="sendMessage")

으로 되어있다면 이 메서드가 /sendMessage라는 경로(path)로 들어오는 요청을 처리하겠다는 것을 의미합니다.

예를 들어, 웹 애플리케이션이 http://localhost:8080에서 실행 중이라면, 사용자가 http://localhost:8080/sendMessage로 접속할 때 이 어노테이션이 붙은 메서드가 실행됩니다.

 

 

 

사용 위치:

  • 컨트롤러 클래스(class) 레벨이나 메서드(method) 레벨에 붙일 수 있습니다.
  • 클래스 레벨에 붙이면 하위 모든 메서드의 기본 URL 경로가 됩니다.

HTTP 메서드 (GET, POST 등):

  • @RequestMapping만 사용하면 기본적으로 모든 HTTP 메서드(GET, POST, PUT, DELETE 등)의 요청을 다 받습니다.
  • 만약 특정 메서드(예: POST)만 받도록 하고 싶다면 method = RequestMethod.POST 속성을 추가하거나, 더 명확하게 @PostMapping("sendMessage")을 사용하는 것이 좋습니다.

 

 

@ResponseBody

@ResponseBody는 메서드의 반환(return) 값을 HTTP 응답 본문(Response Body)에 직접 써서 클라이언트에게 전송하겠다는 것을 의미합니다.

 

주요 역할:

  • 이 어노테이션이 없으면, Spring은 메서드가 반환하는 문자열(String)을 뷰(View) 템플릿 파일의 이름 (예: sendMessage.html이나 sendMessage.jsp)으로 해석하고 해당 뷰를 렌더링하려고 시도합니다.
  • @ResponseBody를 사용하면, Spring은 뷰를 찾는 대신 메서드가 반환한 데이터 자체를 응답으로 보냅니다.

데이터 변환:

  • 반환 타입이 String이면: 문자열 그대로를 text/plain 또는 text/html 형태로 보냅니다.
  • 반환 타입이 자바 객체(Object, DTO, Map, List 등)이면 Spring Boot는 내장된 Jackson 라이브러리를 사용해 해당 객체를 JSON 형식의 문자열로 자동 변환하여 응답 본문에 담아 보냅니다. 이는 REST API를 만들 때 매우 유용합니다.

 

예시

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; // (참고)
import org.springframework.web.bind.annotation.ResponseBody;

@Controller // 이 클래스가 Spring의 컨트롤러임을 나타냅니다.
public class MessageController {

    /**
     * "/sendMessage" 경로로 들어오는 모든 HTTP 요청을 처리합니다.
     * 이 메서드가 반환하는 "메시지 전송 성공!" 문자열은
     * 뷰 이름이 아니라 HTTP 응답 본문(body)에 직접 담겨 전송됩니다.
     */
    @RequestMapping(value = "sendMessage") // 1. /sendMessage URL과 이 메서드를 매핑
    @ResponseBody // 2. 메서드의 반환 값을 뷰가 아닌 데이터 자체로 응답
    public String sendMessage() {
        // (여기에서 실제 메시지 전송 로직이 수행될 수 있습니다.)

        // 뷰 템플릿(sendMessage.html)을 찾는 대신,
        // 이 "메시지 전송 성공!" 문자열이 클라이언트에게 바로 응답됩니다.
        return "메시지 전송 성공!";
    }

    /**
     * 만약 객체를 반환하면 JSON으로 자동 변환됩니다.
     */
    @RequestMapping(value = "messageDetails")
    @ResponseBody
    public MessageDto getMessageDetails() {
        // MessageDto는 단순한 자바 객체(DTO)라고 가정
        return new MessageDto("user123", "안녕하세요!");
    }
}

// (참고용) MessageDto 클래스 예시
class MessageDto {
    private String sender;
    private String content;

    // 생성자, Getter, Setter 등 ...
    public MessageDto(String sender, String content) {
        this.sender = sender;
        this.content = content;
    }
    // (Gatters)
    public String getSender() { return sender; }
    public String getContent() { return content; }
}

 

  • 사용자가 http://.../sendMessage로 요청을 보냅니다.
  • @RequestMapping(value = "sendMessage") 때문에 sendMessage() 메서드가 실행됩니다.
  • 메서드는 "메시지 전송 성공!"이라는 문자열을 반환합니다.
  • @ResponseBody 때문에 Spring은 이 문자열을 뷰 이름으로 해석하지 않고, HTTP 응답 본문에 그대로 담아 사용자에게 보냅니다.
  • 사용자의 브라우저나 클라이언트는 "메시지 전송 성공!"이라는 텍스트 데이터를 받게 됩니다.

 

 

@RestController

만약 클래스 내의 모든 메서드가 뷰 템플릿이 아닌 데이터를 반환(즉, 모든 메서드에 @ResponseBody를 붙여야) 한다면, 매번 @ResponseBody를 붙이는 것은 번거롭습니다.

이때는 클래스 레벨에 @Controller 대신 @RestController를 사용하면 됩니다.

@RestController는 @Controller와 @ResponseBody를 합쳐놓은 어노테이션입니다.

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

@RestController // @Controller + @ResponseBody
public class MessageRestController {

    // @RestController를 사용했기 때문에
    // @ResponseBody를 따로 붙이지 않아도
    // 반환 값이 자동으로 응답 본문에 담겨 전송됩니다.
    @RequestMapping(value = "sendMessage")
    public String sendMessage() {
        return "메시지 전송 성공! (RestController 사용)";
    }

    @RequestMapping(value = "messageDetails")
    public MessageDto getMessageDetails() {
        // 이 객체도 자동으로 JSON으로 변환되어 응답됩니다.
        return new MessageDto("user123", "안녕하세요!");
    }
}

 

 

RESTful CRUD API

Spring Boot, Spring MVC, MyBatis, 그리고 PostgreSQL을 사용하여 RESTful CRUD API를 구현해보겠습니다.

회원 정보(Member)를 관리하는 간단한 API를 만든다고 가정해 보겠습니다.

 

프로젝트 파일들의 구조

src/main/
├── java/com/example/demo/
│   ├── controller/
│   │   └── MemberController.java   // (7. REST 컨트롤러)
│   ├── dto/
│   │   └── Member.java             // (4. 데이터 모델 DTO)
│   ├── mapper/
│   │   └── MemberMapper.java       // (5. MyBatis 매퍼 인터페이스)
│   ├── service/
│   │   ├── MemberService.java      // (6. 서비스 인터페이스)
│   │   └── MemberServiceImpl.java  // (6. 서비스 구현체)
│   └── DemoApplication.java        // (프로젝트 시작점)
│
└── resources/
    ├── mappers/
    │   └── MemberMapper.xml        // (5. MyBatis SQL 쿼리)
    └── application.properties      // (2. 프로젝트 설정)

 

1. 의존성 설정 (pom.xml)

Spring Boot 프로젝트를 생성할 때 다음 의존성들이 필요합니다.

  • Spring Web: Spring MVC와 REST 컨트롤러를 사용하기 위해 필요합니다.
  • MyBatis Spring Boot Starter: Spring Boot와 MyBatis를 쉽게 연동해 줍니다.
  • PostgreSQL Driver: PostgreSQL 데이터베이스에 연결하기 위한 JDBC 드라이버입니다.
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version> </dependency>

    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

 

2. 설정 파일 (application.properties)

src/main/resources/application.properties에 PostgreSQL 연결 정보와 MyBatis 설정 정보를 추가합니다.

# 1. PostgreSQL 데이터베이스 연결 설정
# (사용자 환경에 맞게 URL, username, password를 수정하세요)
spring.datasource.url=jdbc:postgresql://localhost:5432/your_database_name
spring.datasource.username=your_postgres_username
spring.datasource.password=your_postgres_password
spring.datasource.driver-class-name=org.postgresql.Driver

# 2. MyBatis 설정
# DTO 클래스들의 기본 패키지 경로를 지정합니다 (SQL 결과 매핑 시 사용).
mybatis.type-aliases-package=com.example.demo.dto
# SQL 쿼리가 담긴 XML 파일들의 위치를 지정합니다.
mybatis.mapper-locations=classpath:mappers/*.xml

 

3. 데이터베이스 테이블 생성 (PostgreSQL SQL)

CREATE TABLE members (
    id SERIAL PRIMARY KEY,       -- 고유 ID (자동 증가)
    username VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL
);

-- (테스트용 초기 데이터)
INSERT INTO members (username, email) VALUES ('user', 'user@example.com');

 

4. 데이터 모델 (DTO)

데이터베이스 테이블의 데이터를 자바 객체로 주고받기 위한 DTO(Data Transfer Object) 클래스입니다.

src/main/java/com/example/demo/dto/Member.java

package com.example.demo.dto;

// Lombok을 사용하면 @Data 어노테이션 하나로 Getter, Setter, 생성자 등을 대체할 수 있습니다.
// 여기서는 Lombok 없이 명시적으로 작성했습니다.

public class Member {

    private int id;
    private String username;
    private String email;

    // 기본 생성자
    public Member() {
    }

    // 모든 필드를 받는 생성자
    public Member(int id, String username, String email) {
        this.id = id;
        this.username = username;
        this.email = email;
    }

    // --- Getter and Setter ---
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

 

5. MyBatis 매퍼 (Interface + XML)

MyBatis는 Mapper 인터페이스XML 파일이 한 쌍으로 동작하여 SQL을 실행합니다.

src/main/java/com/example/demo/mapper/MemberMapper.java (인터페이스)

  • 실행할 SQL 메서드들을 정의합니다.
  • @Mapper 어노테이션을 붙여 Spring이 MyBatis 매퍼로 인식하도록 합니다.
package com.example.demo.mapper;

import com.example.demo.dto.Member;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper // 이 인터페이스가 MyBatis 매퍼임을 선언
public interface MemberMapper {

    // (C)reate: 회원 추가
    void insertMember(Member member);

    // (R)ead: ID로 회원 조회
    Member findMemberById(int id);

    // (R)ead: 모든 회원 조회
    List<Member> findAllMembers();

    // (U)pdate: 회원 정보 수정
    void updateMember(Member member);

    // (D)elete: 회원 삭제
    void deleteMember(int id);
}

 

src/main/resources/mappers/MemberMapper.xml (SQL 쿼리)

  • 실제 실행될 SQL 쿼리를 작성합니다.
  • namespace는 위에서 만든 매퍼 인터페이스의 전체 경로와 일치해야 합니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.demo.mapper.MemberMapper">

    <insert id="insertMember" parameterType="Member" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO members (username, email)
        VALUES (#{username}, #{email})
    </insert>

    <select id="findMemberById" parameterType="int" resultType="Member">
        SELECT id, username, email
        FROM members
        WHERE id = #{id}
    </select>

    <select id="findAllMembers" resultType="Member">
        SELECT id, username, email
        FROM members
        ORDER BY id ASC
    </select>

    <update id="updateMember" parameterType="Member">
        UPDATE members
        SET username = #{username},
            email    = #{email}
        WHERE id = #{id}
    </update>

    <delete id="deleteMember" parameterType="int">
        DELETE
        FROM members
        WHERE id = #{id}
    </delete>

</mapper>

 

6. 서비스 계층 (Service Layer)

컨트롤러와 매퍼(DAO) 사이에서 비즈니스 로직을 처리하는 계층입니다. (지금 예제에서는 단순 호출만 합니다.)

src/main/java/com/example/demo/service/MemberService.java (인터페이스)

package com.example.demo.service;

import com.example.demo.dto.Member;
import java.util.List;

public interface MemberService {
    Member createMember(Member member);
    Member getMemberById(int id);
    List<Member> getAllMembers();
    Member updateMember(int id, Member member);
    void deleteMember(int id);
}

 

src/main/java/com/example/demo/service/MemberServiceImpl.java (구현체)

  • @Service 어노테이션을 붙여 비즈니스 로직을 담당하는 서비스 빈(Bean)으로 등록합니다.
  • MemberMapper를 주입받아 사용합니다.
package com.example.demo.service;

import com.example.demo.dto.Member;
import com.example.demo.mapper.MemberMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service // 이 클래스가 서비스 레이어의 컴포넌트임을 선언
public class MemberServiceImpl implements MemberService {

    private final MemberMapper memberMapper;

    // 생성자 주입 (Constructor Injection)
    // @Autowired를 생략할 수 있습니다 (Spring 4.3 이상).
    public MemberServiceImpl(MemberMapper memberMapper) {
        this.memberMapper = memberMapper;
    }

    @Override
    public Member createMember(Member member) {
        memberMapper.insertMember(member);
        // insertMember 호출 시 useGeneratedKeys="true" keyProperty="id" 설정 덕분에
        // member 객체에 생성된 id가 자동으로 채워집니다.
        return member;
    }

    @Override
    public Member getMemberById(int id) {
        return memberMapper.findMemberById(id);
    }

    @Override
    public List<Member> getAllMembers() {
        return memberMapper.findAllMembers();
    }

    @Override
    public Member updateMember(int id, Member member) {
        // 전달받은 id를 member 객체에 설정합니다.
        member.setId(id); 
        memberMapper.updateMember(member);
        // 수정된 객체를 다시 조회하여 반환하거나, member 객체를 그대로 반환할 수 있습니다.
        // 여기서는 수정된 member 객체를 반환합니다.
        return member;
    }

    @Override
    public void deleteMember(int id) {
        memberMapper.deleteMember(id);
    }
}

 

7. REST 컨트롤러 (Controller)

클라이언트(웹 브라우저, 모바일 앱 등)의 HTTP 요청을 받아 서비스 계층으로 전달하고, 그 결과를 HTTP 응답으로 반환하는 관문입니다.

src/main/java/com/example/demo/controller/MemberController.java

  • @RestController: 이 클래스의 모든 메서드가 @ResponseBody를 포함하도록 하여, 뷰 템플릿이 아닌 데이터(주로 JSON)를 반환하도록 합니다.
  • @RequestMapping("/api/members"): 이 컨트롤러의 모든 메서드는 /api/members 라는 공통 URL 경로를 가집니다.
package com.example.demo.controller;

import com.example.demo.dto.Member;
import com.example.demo.service.MemberService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController // REST API를 위한 컨트롤러임을 선언
@RequestMapping("/api/members") // 이 컨트롤러의 기본 URL 경로
public class MemberController {

    private final MemberService memberService;

    // 생성자 주입
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    // (C)reate: 회원 생성 (POST /api/members)
    // @RequestBody: HTTP 요청 본문(body)에 담긴 JSON 데이터를 Member 객체로 변환
    @PostMapping
    public ResponseEntity<Member> createMember(@RequestBody Member member) {
        Member createdMember = memberService.createMember(member);
        // 생성된 리소스(createdMember)와 함께 HTTP 201 Created 상태 코드를 반환
        return new ResponseEntity<>(createdMember, HttpStatus.CREATED);
    }

    // (R)ead: 모든 회원 조회 (GET /api/members)
    @GetMapping
    public ResponseEntity<List<Member>> getAllMembers() {
        List<Member> members = memberService.getAllMembers();
        return ResponseEntity.ok(members); // (단축형) return new ResponseEntity<>(members, HttpStatus.OK);
    }

    // (R)ead: 특정 회원 조회 (GET /api/members/{id})
    // @PathVariable: URL 경로에 포함된 값(id)을 파라미터로 받음
    @GetMapping("/{id}")
    public ResponseEntity<Member> getMemberById(@PathVariable int id) {
        Member member = memberService.getMemberById(id);
        if (member != null) {
            return ResponseEntity.ok(member);
        } else {
            // 해당 ID의 회원이 없으면 404 Not Found 반환
            return ResponseEntity.notFound().build();
        }
    }

    // (U)pdate: 회원 정보 수정 (PUT /api/members/{id})
    @PutMapping("/{id}")
    public ResponseEntity<Member> updateMember(@PathVariable int id, @RequestBody Member member) {
        Member updatedMember = memberService.updateMember(id, member);
        if (updatedMember != null) {
            return ResponseEntity.ok(updatedMember);
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    // (D)elete: 회원 삭제 (DELETE /api/members/{id})
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteMember(@PathVariable int id) {
        memberService.deleteMember(id);
        // 삭제 성공 시 HTTP 204 No Content 반환
        return ResponseEntity.noContent().build();
    }
}

 

8. 실행 및 테스트

  1. DemoApplication.java (메인 애플리케이션)을 실행합니다.
  2. Postman, Insomnia 같은 API 테스트 도구나 curl 명령어를 사용하여 API를 테스트합니다.
  • 생성 (POST): POST http://localhost:8080/api/members
    • Body (JSON): {"username": "testuser", "email": "test@example.com"}
  • 전체 조회 (GET): GET http://localhost:8080/api/members
  • 단건 조회 (GET): GET http://localhost:8080/api/members/1 (ID가 1인 회원)
  • 수정 (PUT): PUT http://localhost:8080/api/members/1
    • Body (JSON): {"username": "updatedUser", "email": "updated@example.com"}
  • 삭제 (DELETE): DELETE http://localhost:8080/api/members/1

 

 

 

 

 

 

 

 

728x90