본문 바로가기

백엔드/자바

스프링 부트 에이피아이

반응형

오늘 포스팅에서는 스프링부트의 애플리케이션의 기본구조를 살펴보고 에이피아이 서버 애플리케이션을 만들고 데이터의 생성, 검색, 수정, 삭제 기능을 구현 및 H2 데이터베이스를 추가하는 작업을 보겠습니다.

목차

스프링부트 구조

먼저, 프로젝트 구조를 살펴보면 아래와 같이 세 영역으로 나뉘어 각자의 역할을 담당.

API를 통한 데이터 통신은 HTTP를 통해서 이루어지며 데이터는 다양한 형식 중 JSON 형태로 가장 많이 전송됩니다. 스프링은 제이슨 정보를 활용하기 위해 같은 유입되는 제이슨과 대칭되는 자바 POJO (엔티티)로 전환하는 데 이는 Jackson이라는 프로그램에 의해 자동으로 실행됩니다. 전환은 요청이 올 때 제이슨을 POJO 클래스의 Setter를 통해 반대로 응답은 Getter를 통해 이루어집니다.

필요 디펜던시들

디펜던시는 아래 디펜던시 코드를 pom.xml파일에 넣고 우측상단에 리프레시 버튼을 누릅니다 (아래 그림 참조).

Spring-web

HTTP요청처리를 위한 어노테이션과 웹 서버 구동을 위해 필요.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

H2

인메모리 데이터베이스 디펜던시

<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>

엔티티로부터 데이터베이스에 테이블 생성

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

디펜던시 업데이트 버튼

프로젝트 구조 설정

메인 프로젝트 폴더 아래 아래와 같이 컨트롤러, 엔티티, 서비스 패키지를 생성하고 그 아래 각 클래스를 생성

구현하기

엔티티

엔티티는 데이터를 처리하기 위한 객체로 유입되는 추후에 HTTP를 통해 들어올 때 각 데이터의 이름이 엔티티에 규정된 속성이름과 일치해야 하며, 각 속성에 접근 가능하도록 Getter, Setter를 반드시 추가필요.

package com.example.employee.entity;

public class Employee {
    private int employeeId;
    private String employeeName;
    private String employeeCity;

    public Employee(int employeeId, String employeeName, String employeeCity) {
        this.employeeId = employeeId;
        this.employeeName = employeeName;
        this.employeeCity = employeeCity;
    }

    public int getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }

    public String getEmployeeName() {
        return employeeName;
    }

    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }

    public String getEmployeeCity() {
        return employeeCity;
    }

    public void setEmployeeCity(String employeeCity) {
        this.employeeCity = employeeCity;
    }
}

어노테이션

특정 코드 위에 어노테이션을 표시하면 스프링부트는 자동으로 해당 어노테이션이 가진 기능을 수행합니다.

컨트롤러에 클래스에서 사용되는 어노테이션

▶ @Controller

특정 클래스를 컨트롤러로 표시

@Controller

▶ @ResponseBody

컨트롤러 어노테이션과 함께 사용되며 HTTP 바디를 통해 정보를 전송받기 위해 필요

@ResponseBody

▶ @RestController

@Controller와 @ResponseBody의 기능을 한 번에 수행

@RestController

▶ @Autowired

서비스 클래스 디펜던시 인젝션을 위해 필요

@Autowired

서비스클래스에서 사용되는 어노테이션

▶ @Service

특정 클래스를 서비스로 표시

@Service

엔티티 클래스에서 사용되는 어노테이션

▶ @Entity

JPA 클래스 테이블 생성을 위해 필요

@Entity

▶ @Table

JPA 클래스 테이블 이름은 클래스이름으로 생성되는 데 이를 원하는 이름으로 수정할 때 사용

@Table(name="<name>")

▶ @Id

관계형 데이터베이스에 필요한 주키설정

@Id

▶ @GeneratedValue

주키 자동 생성옵션

@GeneratedValue(strategy = GenerationType.IDENTITY)

▶ @OneToOne

1대 1 관계설정 어노테이션

@OneToOne

▶ @OneTo Many

1대 다 관계설정 어노테이션

@OneToMany

▶ @ManyTo One

다대 1 관계설정 어노테이션

@ManyToOne

▶ @ManyToMany

다대 다 관계설정 어노테이션

@ManyToMany

▶ @JoinColumn

관계설정 시 추가되는 필드명은 속성명을 사용하는 데 이 필드명을 변경할 때 사용

@JoinColumn(name = "<fk_spouse>")

▶ @JoinTable

다대 다 관계설정 시 추가되는 필드명은 속성명을 사용하는 데 이 필드명을 변경할 때 사용

@JoinTable(name = "<tableName>", joinColumns = @JoinColumn(name = "<fieldName>"),
            inverseJoinColumns = @JoinColumn(name = "<fieldName>"))

▶ @JsonIgnore

연결되는 엔티티가 다의 관계를 가질 때 생성되는 데이터가 무한 루프에 빠지는 현상방지

@JsonIgnore

서비스 클래스

컨트롤러 요청에 의해 데이터베이스에 접근하여 데이터를 전송받거나 수정하고 그 결과를 다시 컨트롤러로 반환

▶ 데이터 생성 (예시를 위해 서비스클래스에 직접 생성)

List<Employee> employeeList = new ArrayList<>(Arrays.asList(
  new Employee(1, "John", "Washington"),
  new Employee(2, "Tom", "Waterloo")
));

▶ GET (아이템 리스트)

public List<Employee> getEmployees() {
  return employeeList;
}

▶ GET (특정 아이템)

public Employee getEmployee(int employeeId) {
  return employeeList.stream().filter(e -> (
    e.getEmployeeId() == employeeId
  )).findFirst().get();
}

▶ POST

public void addEmployee(Employee employee) {
  employeeList.add(employee);
}

▶ PUT

public void updateEmployee(Employee employee) {
  List<Employee> tempList = new ArrayList<>();
  for (Employee e: employeeList) {
    if(e.getEmployeeId() == employee.getEmployeeId()) {
      e.setEmployeeName(employee.getEmployeeName());
      e.setEmployeeCity(employee.getEmployeeCity());
    }
    tempList.add(e);
  }
  this.employeeList = tempList;
}

▶ DELETE

public void deleteEmployee(int id) {
  List<Employee> tempList = new ArrayList<>();
  for (Employee e: employeeList) {
    if(e.getEmployeeId() == id) {
      continue;
    }
    tempList.add(e);
  }
  this.employeeList = tempList;
}

▶ 완성코드

package com.example.employee.service;

import com.example.employee.entity.Employee;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class EmployeeService {
    List<Employee> employeeList = new ArrayList<>(Arrays.asList(
            new Employee(1, "John", "Washington"),
            new Employee(2, "Tom", "Waterloo")
    ));

    public List<Employee> getEmployees() {
        return employeeList;
    }

    public Employee getEmployee(int employeeId) {
        return employeeList.stream().filter(e -> (
                e.getEmployeeId() == employeeId
        )).findFirst().get();
    }

    public void addEmployee(Employee employee) {
        employeeList.add(employee);
    }

    public void updateEmployee(Employee employee) {
        List<Employee> tempList = new ArrayList<>();
        for (Employee e: employeeList) {
            if(e.getEmployeeId() == employee.getEmployeeId()) {
                e.setEmployeeName(employee.getEmployeeName());
                e.setEmployeeCity(employee.getEmployeeCity());
            }
            tempList.add(e);
        }
        this.employeeList = tempList;
    }

    public void deleteEmployee(int id) {
        List<Employee> tempList = new ArrayList<>();
        for (Employee e: employeeList) {
            if(e.getEmployeeId() == id) {
                continue;
            }
            tempList.add(e);
        }
        this.employeeList = tempList;
    }
}

컨트롤러 클래스

컨트롤러는 위 그림에서 처럼 프레젠테이션 영역에 속하며 HTTP요청의 엔드포인트(주소)를 생성하고 해당 엔드포인트에 대한 요청을 서비스로 전달합니다. 이후 서비스에서 처리된 정보를 프론트로 전송합니다.

▶ GET (아이템 리스트)

@RequestMapping("/employees")
public List<Employee> getEmployees() {
  return employeeService.getEmployees();
}

또는

@GetMapping("/employees")
public List<Employee> getEmployees() {
  return employeeService.getEmployees();
}

▶ GET (특정 아이템)

@RequestMapping("/employees/{id}")
public Employee getEmployee(@PathVariable int id) {
  return employeeService.getEmployee(id);
}

또는

@GetMapping("/employees/{id}")
public Employee getEmployee(@PathVariable int id) {
  return employeeService.getEmployee(id);
}

▶ POST

@RequestMapping(value = "/employees", method = RequestMethod.POST)
public void addEmployee(@RequestBody Employee employee) {
  employeeService.addEmployee(employee);
}

또는

@PostMapping("/employees")
public void addEmployee(@RequestBody Employee employee) {
  employeeService.addEmployee(employee);
}

▶ PUT

@RequestMapping(value = "/employees/{id}", method = RequestMethod.PUT)
public void updateEmployee(@PathVariable int id, @RequestBody Employee employee) {
  employeeService.updateEmployee(employee);
}

또는

@PutMapping("/employees/{id}")
public void updateEmployee(@PathVariable int id, @RequestBody Employee employee) {
  employeeService.updateEmployee(employee);
}

▶ DELETE

@RequestMapping(value = "/employees/{id}", method = RequestMethod.DELETE)
public void deleteEmployee(@PathVariable int id) {
  employeeService.deleteEmployee(id);
}

또는

@DeleteMapping("/employees/{id}")
public void deleteEmployee(@PathVariable int id) {
  employeeService.deleteEmployee(id);
}

▶ 완성코드

package com.example.employee.controller;
import com.example.employee.entity.Employee;
import com.example.employee.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Controller
@ResponseBody
//@RestController
public class EmployeeController {
    
    @Autowired
    EmployeeService employeeService;

    @RequestMapping("/employees")
    //@GetMapping("/employees")
    public List<Employee> getEmployees() {
        return employeeService.getEmployees();
    }

    @RequestMapping("/employees/{id}")
    //@GetMapping("/employees/{id}")
    public Employee getEmployee(@PathVariable int id) {
        return employeeService.getEmployee(id);
    }

    @RequestMapping(value = "/employees", method = RequestMethod.POST)
    //@PostMapping("/employees")
    public void addEmployee(@RequestBody Employee employee) {
        employeeService.addEmployee(employee);
    }

    @RequestMapping(value = "/employees/{id}", method = RequestMethod.PUT)
    //@PutMapping("/employees/{id}")
    public void updateEmployee(@PathVariable int id, @RequestBody Employee employee) {
        employeeService.updateEmployee(employee);
    }

    @RequestMapping(value = "/employees/{id}", method = RequestMethod.DELETE)
    //@DeleteMapping("/employees/{id}")
    public void deleteEmployee(@PathVariable int id) {
        employeeService.deleteEmployee(id);
    }
}

데이터베이스(H2) 추가하기

속성설정

application.properties파일을 열고 아래 데이터베이스 설정 코드를 삽입합니다.

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:<dbName>
spring.datasource.username=<name>
spring.datasource.password=<password>
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

※ 아래 옵션은 콘솔에 sql명령어를 표시하는 옵션과 표시되는 명령어 포맷 옵션

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

디펜던시 추가 (위 디펜던시 항목 참조)

엔티티 어노테이션 추가

제이피아이을 활용하여 테이블을 생성하기 위해 @Entity, @Id 어노테이션과 기본 컨스트럭터를 아래와 같이 추가필요

package com.example.testemployee.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int employeeId;
    private String employeeName;
    private String employeeCity;

    public Employee() {
    }

    public Employee(int employeeId, String employeeName, String employeeCity) {
        this.employeeId = employeeId;
        this.employeeName = employeeName;
        this.employeeCity = employeeCity;
    }

    public int getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }

    public String getEmployeeName() {
        return employeeName;
    }

    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }

    public String getEmployeeCity() {
        return employeeCity;
    }

    public void setEmployeeCity(String employeeCity) {
        this.employeeCity = employeeCity;
    }
}

연결확인

설정 후 앱을 가동하고 브라우저에 아래 주소로 접속

http://localhost:8080/h2-console

URL, 이름, 비밀번호를 application.properties에 입력한 값과 동일하게 하고 연결 선택

레포지토리 추가

레포지토리 패키지를 추가하고 그 아래 JPA레포지토리를 확장할 인터페이스를 생성합니다.

package com.example.employee.repository;
import com.example.employee.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

}

서비스 클래스 수정

서비스 클래스에 생성한 레포지토리 인터페이스를 속성으로 추가하고 @Autowired 어노테이션을 추가 후 각 메서드 내 코드를 아래와 같이 교체

package com.example.employee.service;

import com.example.employee.entity.Employee;
import com.example.employee.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class EmployeeService {
  @Autowired
  EmployeeRepository employeeRepository;

  public List<Employee> getEmployees() {
    return employeeRepository.findAll();
  }

  public Employee getEmployee(int employeeId) {
    return employeeRepository.findById(employeeId).orElseThrow(() -> new RuntimeException("Not found"));
  }

  public void addEmployee(Employee employee) {
    employeeRepository.save(employee);
  }

  public void updateEmployee(Employee employee) {
    /*
    * jpa는 저장 명령어만 존재
    * 전송하는 ID가 DB에 존재하는 경우 업데이트
    * 존재하지 않는 경우 새 아이템 저장
    */
    employeeRepository.save(employee);
  }

  public void deleteEmployee(int id) {
    employeeRepository.delete(employeeRepository.getReferenceById(id));
  }
}

케스케이드

케스케이드는 부모엔티티와 자식엔티티에 생성되는 데이터의 일관성을 유지하기 위한 옵션으로 아래와 같이 세 가지 옵션이 존재합니다.

▶ ALL

부모 엔티티에 실행되는 모든 명령어를 자식 엔티티에도 실행 (생성, 삭제, 수정,...)

@OneToOne(cascade = CascadeType.ALL)

▶ PERSIST

부모 엔티티에 실행되는 저장 명령어만 자식 엔티티에도 실행 (나머지 명령어 실행 불가)

@OneToOne(cascade = CascadeType.PERSIST)

▶ REMOVE

부모 엔티티에 실행되는 삭제 명령어만 자식 엔티티에도 실행 (나머지 명령어 실행 불가)

@OneToOne(cascade = CascadeType.REMOVE)

▶ REFRESH

부모 엔티티에 실행되는 갱신 명령어만 자식 엔티티에도 실행 (나머지 명령어 실행 불가)

@OneToOne(cascade = CascadeType.REFRESH)

▶ DETACH

부모 엔티티에 실행되는 디타치 명령어만 자식 엔티티에도 실행 (나머지 명령어 실행 불가)

@OneToOne(cascade = CascadeType.DETACH)

▶ MERGE

부모 엔티티에 실행되는 합치기 명령어만 자식 엔티티에도 실행 (나머지 명령어 실행 불가)

@OneToOne(cascade = CascadeType.MERGE)

※ 중괄호를 사용하여 복수의 케스테이드 옵션 추가도 가능

@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})

펫치타입

펫치타입은 부모엔티티 데이터를 부를 때 연결된 자식엔티티도 같이 호출하는 옵션으로 아래와 같이 두 가지 옵션이 존재합니다.

▶ EAGER

부모 엔티티의 데이터를 호출할 때 연결된 자식 데이터도 같이 호출 (one to one, many to one 관계에서 기본옵션)

@OneToMany(fetch = FetchType.EAGER)

▶ LAZY

부모 엔티티의 데이터를 호출할 때 연결된 자식 데이터 호출하지 않음 (one to many, many to many 관계에서 기본옵션)

@OneToOne(FetchType.LAZY)

관계추가하기

다음으로 데이터베이스에 관계를 추가해 보겠습니다.

▶ 1대 1 관계

참조 엔티티를 생성하고 생성자, 엔티티가 가지는 속성 및 Getter, Setter를 추가한 뒤 주 엔티티를 속성으로 추가하고 아래와 같이 @OneToOne어노테이션에 'mappedBy'옵션을 사용하여 주 엔티티에 추가할 보조엔티티 필드와 연결 (해당 작업 생략 시 주 엔티티에서 보조만 참조가능한 일방향 관계 성립) 및 Getter, Setter 추가. 

@OneToOne(mappedBy = "spouse")
private Employee employee;

생성한 속성 Getter, Setter 추가


양방향 관계가 성립 후 보조 엔티티 캐스케이드를 추가하면 보조엔티티를 통해 주엔티티를 저장하거나 삭제도 가능

@OneToOne(mappedBy = "spouse", cascade = CascadeType.ALL)
private Employee employee;

삭제 시 주 엔티티 데이터를 남겨두고 싶은 경우, 케스케이드에서 delete를 제외

@OneToOne(mappedBy = "employeeDetail", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
private Employee employee;

삭제 메서드에서 널 값지정으로 두 엔티티 간 링크 제거

public void deleteEmployeeDetail(int id) {
    EmployeeDetail employeeDetail = entityManager.find(EmployeeDetail.class, id);
    employeeDetail.getEmployee().setEmployeeDetail(null);
    entityManager.remove(employeeDetail);
}

주 엔티티로 이동하여 생성한 엔티티를 속성에 @OneToOne 어노테이션을 추가하고 주 엔티티의 데이터가 삭제될 때 연결된 엔티티의 데이터도 삭제되도록 'cascade' 옵션을 추가 (@JoinColumn 어노테이션은 필드명 수정 옵션) 및 생성한 속성 Getter, Setter 추가

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "fk_spouse")
private Spouse spouse;

▶ 다대 1 관계

연결할 엔티티를 생성하고 생성자, 엔티티가 가지는 속성 및 Getter, Setter를 추가한 뒤 1의 관계 쪽 엔티티를 속성으로 추가하고 아래와 같이 @ManyToOne어노테이션에 옵션을 사용하여 연결 및 Getter, Setter를 추가. 다의 관계를 가지는 엔티티는 'cascade'옵션을 사용할 때 데이터 생성 시 무한루프에 빠지는 것을 방지하기 위해 @JsonIgnore 어노테이션 추가필요

추가된 필드를 포함하는 컨스트럭터 생성

▶ 1대 다 관계

1의 관계 쪽 엔티티에 다수의 엔티티를 아래와 같이 리스트로 추가하고 @OneToMany 어노테이션으로 연결 (주 엔티티의 데이터가 삭제될 때 연결된 엔티티의 데이터도 삭제되도록 'cascade' 옵션을 추가) 및 Getter, Setter 추가

@OneToMany(cascade = CascadeType.ALL)
private List<Address> addresses;

추가로 서비스 클래스의 주 데이터 생성하는 코드를 자식 엔티티 값이 반영되도록 아래와 같이 수정

package com.example.employee.service;

import com.example.employee.entity.Address;
import com.example.employee.entity.Employee;
import com.example.employee.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class EmployeeService {
  @Autowired
  EmployeeRepository employeeRepository;

  public List<Employee> getEmployees() {
    return employeeRepository.findAll();
  }

  public Employee getEmployee(int employeeId) {
    return employeeRepository.findById(employeeId).orElseThrow(() -> new RuntimeException("Not found"));
  }

  //updated
  public void addEmployee(Employee employee) {
    ArrayList<Address> addresses = new ArrayList<>();
    for (Address address : employee.getAddresses()) {
      addresses.add(new Address(
        address.getZip(),
        address.getCity(),
        address.getState(),
        address.getCountry()
      ));
    }
    employee.setAddresses(addresses);
    employeeRepository.save(employee);
  }

  public void updateEmployee(Employee employee) {
    employeeRepository.save(employee);
  }

  public void deleteEmployee(int id) {
    employeeRepository.delete(employeeRepository.getReferenceById(id));
  }
}

@OneToMany(mappedBy = "employee", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
private List<Project> projects;
    
public void add(Project project) {
    if (project == null) {
        projects = new ArrayList<>();
    }
    projects.add(project);
    project.setEmployee(this);
}

 

▶ 다대 다 관계

연결할 엔티티를 생성하고 생성자, 엔티티가 가지는 속성 및 Getter, Setter를 추가한 뒤 연결할 엔티티를 속성으로 추가하고 아래와 같이 @ManyToMany어노테이션에 'mappedBy'옵션을 사용하여 주 엔티티에 추가할 보조엔티티 필드와 연결 (해당 작업 생략 시 주 엔티티에서 보조만 참조가능한 일방향 관계 성립) 및 Getter, Setter 추가.

주 엔티티에 생성한 엔티티를 아래와 같이 리스트로 추가하고 @ManyToMany 어노테이션으로 연결 (주 엔티티의 데이터가 삭제될 때 연결된 엔티티의 데이터도 삭제되도록 'cascade' 옵션을 추가) 및 Getter, Setter 추가 

@ManyToMany(cascade = CascadeType.ALL)   
private List<Project> projects;

※ 아래 코드사용하여 필드명 변경가능

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "employee_project", joinColumns = @JoinColumn(name = "fk_employee"),
            inverseJoinColumns = @JoinColumn(name = "fk_project"))
private List<Project> projects;

다대 다 관계에서 데이터가 생성 또는 삭제될 때 연결된 두 테이블 모두 수정사항을 반영하기 위해 아래 두 함수를 주 테이블에 추가.

public void removeProject(Project project) {
  this.projects.remove(project);
  project.getEmployee().remove(project);
}

public void addProject(Project project) {
  this.projects.add(project);
  project.getEmployee().add(this);
}

에러처리

잘못된 요청 등으로 인해 에러가 발생한 경우 에러 응답은 기본적으로 HTML형태로 전송됩니다. HTML형태로 전송되는 에러 메시지는 가독성이나 활용성이 떨어지기 때문에 제이슨 형태의 응답으로 바꾸어보겠습니다.

어노테이션

▶ @ExceptionHandler

에러처리

@ExceptionHandler

▶ @ControllerAdvice

글로벌 에러 처리

@ControllerAdvice

엔티티 생성

에러 정보를 담은 POJO클래스 생성

▶ 코드

package com.example.employee.common;

public class ErrorRes {
    private int status;
    private String message;
    private long timestamp;

    public ErrorRes() {
    }

    public ErrorRes(int status, String message, long timestamp) {
        this.status = status;
        this.message = message;
        this.timestamp = timestamp;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
}

에러처리 클래스 생성

에러처리 클래스 생성하고 'RuntimeException'을 확장

컨스트럭터 추가

▶ 코드

package com.example.employee.common;

public class NotFound extends RuntimeException  {
    public NotFound(String message) {
        super(message);
    }

    public NotFound(String message, Throwable cause) {
        super(message, cause);
    }

    public NotFound(Throwable cause) {
        super(cause);
    }
}

컨트롤러 클래스에 에러처리 메서드 추가

@ExceptionHandler
public ResponseEntity<ErrorRes> handleError(NotFound ex) {
    ErrorRes err = new ErrorRes();
    err.setStatus(HttpStatus.NOT_FOUND.value());
    err.setMessage(ex.getMessage());
    err.setTimestamp(System.currentTimeMillis());
    return new ResponseEntity<>(err, HttpStatus.NOT_FOUND);
}

@ExceptionHandler
public ResponseEntity<ErrorRes> handleException(Exception ex) {
    ErrorRes err = new ErrorRes();
    err.setStatus(HttpStatus.BAD_REQUEST.value());
    err.setMessage(ex.getMessage());
    err.setTimestamp(System.currentTimeMillis());
    return new ResponseEntity<>(err, HttpStatus.BAD_REQUEST);
}

글로벌 에러처리

위와 같이 컨트롤러에 추가하는 방식은 컨트롤러마다 일일이 작업이 필요한데 @ControllerAdvice 어노테이션을 사용하여 애플리케이션 단위에서 에러처리가 가능합니다.

 

에러처리 클래스를 생성하고 @ControllerAdvice 어노테이션추가, 컨트롤러에 작성했던 에러처리 메서드를 이 클래스로 옮깁니다.

▶ 코드

package com.example.employee.common;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler
    public ResponseEntity<ErrorRes> handleError(NotFound ex) {
        ErrorRes err = new ErrorRes();
        err.setStatus(HttpStatus.NOT_FOUND.value());
        err.setMessage(ex.getMessage());
        err.setTimestamp(System.currentTimeMillis());
        return new ResponseEntity<>(err, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler
    public ResponseEntity<ErrorRes> handleException(Exception ex) {
        ErrorRes err = new ErrorRes();
        err.setStatus(HttpStatus.BAD_REQUEST.value());
        err.setMessage(ex.getMessage());
        err.setTimestamp(System.currentTimeMillis());
        return new ResponseEntity<>(err, HttpStatus.BAD_REQUEST);
    }
}

이상으로 스프링 부트를 활용하여 에이피아이 애플리케이션을 만들어 보았습니다.


참고

Commands (h2database.com)

 

Commands

 

www.h2database.com

 

728x90
반응형