🍃Spring

    [Spring] 스프링 부트에서 Chat GPT API 사용해보기

    OPEN AI에서 지원하는 Chat GPT의 API를 사용해서 프롬프트에 대해 응답을 받는 경험을 공유하고자 쓰는 글입니다. 준비 사항 저는 아래의 라이브러리를 사용했습니다. https://github.com/TheoKanning/openai-java GitHub - TheoKanning/openai-java: OpenAI GPT-3 Api Client in Java OpenAI GPT-3 Api Client in Java. Contribute to TheoKanning/openai-java development by creating an account on GitHub. github.com 그리고, 프로젝트에서는 gradle을 사용하기 때문에 아래 의존성을 추가해줍니다. implementation '..

    [Spring] IoC와 DI (2/2)

    DI 구현 방법 필드 주입 @Service public class BurgerService { @Autowired private BurgerRecipe burgerRecipe; } 변수 선언부에 @Autowired 어노테이션을 붙인다. 장점 사용하기 편하다 단점 단일 책임 원칙 위반의 가능성이 있다. @Autowired 선언만 하면 되므로 의존성을 주입하기 쉽다. 따라서, 하나의 클래스가 많은 책임을 갖게 될 가능성이 높다. 의존성이 숨는다. 생성자 주입에 비해 의존 관계를 한 눈에 파악하기 어렵다. DI 컨테이너와의 결합도가 커지고, 테스트하기 어렵다. 불변성을 보장할 수 없다. 순환 참조가 발생할 수 있다. 수정자 주입 @Service public class BurgerService { private..

    [Spring] IoC와 DI (1/2)

    Inversion of Control 제어의 역전이라고 한다. 스프링 애플리케이션에서 스프링 빈의 생성과 의존관계 설정, 사용 및 제거 등의 작업을 애플리케이션 코드가 아닌 스프링 컨테이너가 담당한다. 이를 스프링 컨테이너가 코드 대신 빈에 대한 제어권을 갖고 있다고 해서 IoC라고 부른다. 스프링 컨테이너를 IoC 컨테이너라고 부르기도 한다. IoC 컨테이너 스프링에서 IoC를 담당하는 컨테이너를 빈 팩토리, DI 컨테이너, Application Context라고 부른다. 빈(오브젝트)의 생성과 빈 사이의 런타임 관계를 설정하는 DI의 관점에서 컨테이너를 빈 팩토리 또는 DI 컨테이너라고 부른다. 그러나 스프링 컨테이너는 단순히 DI작업보다 더 많은 작업을 하는데, DI를 위한 빈 팩토리에 여러가지 기..

    [Spring] 스프링 빈 스코프 (2/2)

    스프링 빈 스코프 스프링에서 싱글톤과 프로토타입 빈 스코프를 제공하고 있으며, 스프링 MVC 웹 애플리케이션을 사용할 경우 웹 스코프를 제공한다. 싱글톤 싱글톤 빈은 스프링 컨테이너에서 한 번만 생성되며, 컨테이너가 사라질 때 제거된다. 생성된 하나의 인스턴스는 Spring Beans Cache에 저장되고, 해당 빈에 대한 요청과 참조가 있으며 캐시된 객체를 반환한다. 하나만 생성되기 떄문에 동일 참조를 보장한다. 기본적으로 모든 빈은 스코프가 명시적으로 지정되지 않으면 싱글톤이다. 대상 클래스에 @Scope("singletone")을 붙이면 된다. 싱글톤으로 적합한 객체는 아래와 같다. 상태가 없는 공유 객체 읽기 전용으로만 상태를 가진 객체 쓰기가 가능한 상태를 지니면서도 사용 빈도가 매우 높은 객체..

    [Spring] 스프링 빈 (1/2)

    스프링 빈 스프링 컨테이너에 의해 관리되는 POJO 자바 객체 스프링 컨테이너 스프링 빈의 생명 주기를 관리하며, 생성된 스프링 빈에게 추가 기능을 제공한다. IoC와 DI 원리가 여기에 적용된다. 개발자는 객체를 생성할 때 new 연산자, 팩토리 메서드 호출 등을 사용하지만, 스프링 컨테이너를 사용하면 해당 역할을 대신해준다. 즉 제어의 흐름을 외부에서 관리한다. 또한, 객체들의 의존 관계를 스프링 컨테이너가 런타임 과정에서 알아서 만들어 준다. 스프링 빈 등록 컴포넌트 스캔 @Component 어노테이션이 붙은 클래스를 스캔해서 빈으로 등록한다. @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed publ..

    [Spring] 테스트 클렌징시 deleteAll()을 사용할 때의 주의점

    테스트 코드를 작성하면서, 테스트 간 독립성을 보장하기 위해 트랜잭션 롤백 클렌징을 사용하지 않고 @AfterEach를 통해 리포지토리 레벨에서 delete 메서드를 통해 given 절에서 생성했던 픽스쳐를 제거한다고 해보자. 우선 나의 경우 Order 엔티티 픽스쳐를 지우기 위해 아무 의심 없이 deleteAll() 메서드를 사용했다. 그래서 @AfterEach의 메서드를 아래와 같이 작성했다. @AfterEach void tearDown() { orderRepository.deleteAll(); productRepository.deleteAll(); } AS-IS 내가 테스트를 할 메서드는 아래와 같다. @DisplayName("주문번호 리스트를 받아 주문을 생성한다.") @Test void crea..

    [Spring] WebMvcTest에서 발생하는 SpringSecurity 의존성 문제 해결하기

    AS-IS UserController의 테스트를 해보기 위해 @ExtendWith(SpringExtension.class) @WebMvcTest(controllers = UserController.class, 를 사용해서 테스트 코드를 작성하던 중, 다음과 같은 예외가 발생했다. Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '~~' defined in file [~~]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.b..

    [Redis] Redis 캐시를 사용한 JWT 리프레시 토큰 관리하기

    이번 프로젝트부터 Spring Security를 사용하여 로그인을 구현하게 되었다. 그래서 그중 가장 많이 사용하는 JWT 토큰의 로그인 방식을 구현했다. 전체적인 흐름은 아래와 같이 구현했다. 클라이언트가 로그인을 요청 서버에서 유효성을 체크 후 응답으로 액세스 토큰과 리프레시 토큰을 반환한다. 유효 시간은 각각 2시간, 14일로 설정했다. 서버에서는 로그인 응답을 할 때, 아이디와 리프레시 토큰을 RefreshToken 엔티티를 생성하고 RDB에 저장한다. 시간이 지나 엑세스 토큰이 만료된다. 엑세스액세스 토큰이 만료되어도 리프레시 토큰의 유효기간이 길기 때문에 액세스 토큰 재발급 경로로 요청 시, 액세스 토큰 내에 페이로드로 담긴 아이디와 리프레시 토큰으로 유효성 검증을 한다. 이때 DB에 저장했던..

    [Spring Security] CORS 문제 해결하기

    리액트에서 서버로 요청을 보내는데 악명 높은 CORS 문제를 만났다. CORS란? 한 웹 어플리케이션이 다른 포트에 있는 자원에 접근하고자 할때, 접근 권한을 주어야한다. 예를 들어, React의 http://localhost:3000 에서 Spring의 http://localhost:8080으로 데이터를 보내거나 받고 싶은 경우, CORS 접근 권한이 필요한 것이다. 이는 브라우저에 구현되어 있기 때문에 Postman과 같은 툴을 이용해 API를 보내면 발생하지 않는다. 마주한 오류는 아래와 같다. Access to fetch at 'http://localhost:8080/login' from origin 'API 호출한 IP' has been blocked by CORS policy: Response..

    [Spring] 조회수 필드에 대해 동시성 문제 해결하기

    Question 클래스는 질문 게시글에 대한 엔티티 클래스이다. 프런트의 요청으로 조회수를 추가할 일이 생겼고, 우선 다음과 같이 조회수 필드를 추가했다. Question.java public class Question extends BaseTimeEntity { //다른 필드 @ColumnDefault("0L") private long viewCount; // 다른 필드 } 고려해 볼 만한 사항 우선 래퍼 클래스로 선언할지, 기본 타입으로 선언할지 고민했으나, 아무래도 조회수의 경우 기본값이 null이면 안된다고 생각해서 기본 타입으로 선언했다. 동시성 문제가 발생할 여지가 있다. 조회수 증가 메서드는 게시글 조회할 때 실행되는 메서드이므로, 같은 시점에 같은 게시글에 대한 요청이 들어올 경우, 만약 ..