Server 121

Spring - Service Layer에서 Storage를 다룰 때 트랜잭션 처리하기! (커넥션, 락 과점유 피하기!)

DB 자원을 등록할 때 파일을 추가로 업로드하는 경우가 있다. 조회를 할 때 파일도 추가로 조회해야 하는 경우도 있다. Service에서 스토리지 다루기 아래 예시를 보자. @Service @RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; private final MemberResourceService resourceService; @Transactional public Member create(CreateMemberRequest request) { Member member = new Member(request.getUsername()); memberRepository.s..

Server/Spring JPA 2022.10.25

Spring - 다중 데이터 소스 환경에서 spring.sql.init이 동작하지 않는 이슈 해결

테스트 코드를 돌릴 때 docker-compose에 MySQL을 띄워서 로컬에서만 테스트하고 있었다. 해당 부분은 자동화할 필요가 있다고 느껴서 테스트 코드에 H2 및 테스트 자동화를 적용하기로 했다.테스트를 수행하기 전에 DDL 스크립트를 실행하도록 처리해야 했다.구현src/test/resources/application.yml에서 아래의 환경 설정을 적용했다.spring: jpa: hibernate: ddl-auto: none sql: init: schema-locations: classpath:sql/order-schema.sql mode: always해당 설정은 Hibernate에서 제공하는 DDL 자동 생성 기능을 사용하지 않고 직접 정의한 sql이 실행..

Server/Spring 2022.10.22

Spring Transaction - Propagation(전파 속성)

Transaction Propagation Spring Transaction의 Propagation(전파 범위)에 대해서 알아보자. 트랜잭션 전파 범위는 트랜잭션을 시작하거나 기존 트랜잭션에 참여하는 방법과 롤백되는 범위 등에 대한 속성값이다. 종류는 다음의 7가지가 있다. REQUIRED(default) REQUIRES_NEW MANDATORY SUPPORTS NESTED NEVER REQUIRED default 속성이다. Required는 부모 트랜잭션이 존재한다면 부모 트랜잭션에 합류한다. 그렇지 않다면 새로운 트랜잭션을 만든다. 중간에 자식 / 부모에서 예외가 발생한다면 자식과 부모 모두 rollback 한다. REQUIRES_NEW 무조건 새로운 트랜잭션을 만든다. nested한 방식으로 메소드..

Server/Spring JPA 2022.10.04

Spring - Request DTO에 NoArgsConstructor의 AccessLevel을 어떻게 설정할까?!

RequestDTO에 NoArgsConstructor(기본 생성자)가 반드시 필요하다. 이유는 @RequestBody를 사용할 때 Jackson2HttpMessageConverter를 사용하기 때문이다. 그러면 RequestDTO에 Lombok의 @NoArgsConstructor 애노테이션을 설정해주었다. @Getter @AllArgsConstructor @NoArgsConstructor public class CreateArtistRequest { @NotBlank private String name; } 이거면 충분할까? 그렇지 않다! Reflection Jackson2HttpMessageConverter는 기본 생성자를 통해 DTO의 인스턴스를 생성한다. 중요한 것은 이때 사용되는 것이 Reflec..

Server/Spring 2022.10.03

Spring - Unique Key를 제어하는 방법 (비교) + Exception 처리!

JPA를 사용하면 아래와 같이 엔터티를 구현합니다. @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Account implements Serializable { @Id private String id; private String password; private String name; @Column(unique = true) private String phone; @Enumerated(value = EnumType.STRING) private AccountStatus status; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "account_role", joinColumns..

Server/Spring 2022.09.28

Spring - 이미지 조회를 메모리 효율적으로 구현하기! (+ InputStreamResource)

기존에 Image를 조회하는 Controller Method가 있었다. 예시를 살펴보자. Byte[]를 반환 @GetMapping("/attachments/{no}") @ResponseStatus(HttpStatus.OK) public byte[] getImage(..., @PathVariable long no) { // ... 생략 return attachmentStorage.getImageContent(no); } 위와 같이 Controller method에서 byte[]로 이미지의 binary data를 반환해주면, 사용하는 쪽에서 와 같은 방식으로 이미지를 렌더링할 수 있다. 문제는 byte[]를 반환하기 위해서는 해당 파일을 앱서버에서 모두 읽어서 메모리에 보관한 이후에 클라이언트에 반환할 수 ..

Server/Spring 2022.09.27

Spring - Exception 처리 전략 적용기 (+ 에러 코드 문서화!)

최근 Exception에 대한 처리에 관심이 많아져서 관련 포스팅을 썼었다. REST에서 예외를 처리하는 다양한 방법! REST API에서 직접 정의한 Error code를 사용해야 하는 이유! 이번에 공부한 내용들과 추가로 참고 자료를 활용해서 사이드 프로젝트에서 예외 처리 전략을 풀어내보았고, 만족스러운 결과를 얻었다. 코드를 살펴보자. 코드 ErrorResponse 먼저 예외가 발생했을 때 Client 측에 내려줄 ErrorResponse를 정의한다. @Getter @AllArgsConstructor public class ErrorResponse { private final String code; private final String message; } ErrorCode 추가로 위와 같이 Erro..

Server/Spring 2022.09.19

JUnit - Enum으로 Test Fixture 사용하기! (feat. Object Mother, Test Data Builder)

테스트 데이터 생성 테스트 코드를 작성할 때 테스트 데이터는 어떻게 생성해야 할까..? 예시를 들어보자! 유저 조회, 게시글 생성, 댓글 등록 등 기능을 테스트할 때 모두 Member 생성이 필요하다. 각 테스트에서 일일이 생성자로 Member를 호출해도 되지만, 이를 재사용할 수 있다면 유지보수성이 좋은 코드가 된다. 잘 알려진 방법으로는 아래 2가지가 있다. 1. Test Data Builder 패턴 Test Data Builder 패턴은 Builder를 활용해서 테스트 데이터를 생성하는 패턴이다. public class UserStubGenerator { public static UserStub.Builder generateUserStub() { return UserStub.builder() .lo..

Server/JUnit, Spock 2022.09.16

JPA - Update(수정) 시 save() 메서드를 호출하는 것이 좋을까?

JPA를 사용하면 트랜잭션 범위 안에서 Dirty Checking이 동작한다. 따라서 save() 메서드를 호출하지 않아도 값이 알아서 수정되고 반영된다. 그렇다면 save()를 호출하는 것이랑 어떤 차이가 있는 지 알아보자. 차이 먼저 @Transactional만을 사용한 예제를 보자. @Transactional public Notice update(Long noticeId, String content) { Notice notice = noticeRepository.findById(noticeId).get(); notice.setContent(content); } 다음은 repository.save() 메서드를 사용한 예제를 보자. public Notice update(Long noticeId, Str..

Server/Spring JPA 2022.09.15

JPA - TestEntityManager를 활용한 Repository 테스트

Repository 테스트를 작성할 때 결과를 검증하기 위해서 어떤 도구를 사용해야 할까? 일반적으로 Repository.save() 기능을 테스트한다고 하면, Repository.find_을 사용해서 검증하기 쉽다. 하지만 이는 적절치 않다. 가령, userRepository를 테스트하는 코드에서 userRepository를 사용하면 First 원칙 중 I(Isolated - 고립성)이 깨지게 된다. 그래서 나는 JdbcTemplate을 사용했다. 기존의 코드 @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class AccountRepositoryTest { private AccountRe..

Server/JUnit, Spock 2022.09.14