평소에 DTO에서 `from(Entity entity)`메서드를 만들어 사용하고 있었다.코드 리뷰를 하며, '이 코드에서 DTO와 Entity의 변환에 대한 책임은 누구에게 있나요?'라는 질문에 답을 하지 못했다. 다시 생각해보니 DTO가 두 가지 책임을 동시에 가지고 있었다:Presentation 계층의 데이터 구조 역할Entity 변환 로직 처리단일 책임 원칙(SRP)을 위반하고 있었다. 더 큰 문제는 Presentation 계층(DTO)이 Domain 계층(Entity)을 직접 의존한다는 점이었다. 간단한 매핑이기 때문에 Mapper는 오버엔지니어링이라고 생각했지만, 계층 간 경계를 무너뜨리는 기본을 안 지킨 설계였다. 구체적으로 왜 DTO에서의 변환은 잘못된 것인지 알아보고, 앞으론 어떻게 작성..
Spring
프로젝트를 진행하면서 DB에 레코드를 추가하거나 변경할 때는 `@Transactional` 어노테이션을 적용했다. 레코드를 조회만 할 경우에는 처음엔 `@Transactional`을 적용하지 않았다. 근데 자동완성 과정에서 `@Transactional(readOnly = true)`를 발견했다. 원자성을 보장하기 위한 `@Transactional` 어노테이션을 조회만 있는 비즈니스 로직에서 사용할 필요가 있을까? 트랜잭션의 특징, ACID트랜잭션은 특징은 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)의 4가지로 줄여서 ACID로 부른다. ACID는 짧게 정리해보자. 원자성(Atomicity): 트랜잭션과 관련된 작업들이 부분적으로 실..
사이드 프로젝트를 진행 중 비즈니스 로직을 담당하는 Service단에 예외 처리 로직이 들어가 관심사가 분리되지 않고, 예외 처리 로직도 로그만 남기고 예외를 다시 던지는 의미없는 예외 던지기라는 피드백을 받았다. 실제로 Service 코드엔 15개의 메서드에 동일한 try-catch 패턴이 있었다. @Transactionalpublic BookResponse updateBook(Long id, BookRequest request) { try { // 입력값 검증 bookValidator.validateUpdateBookRequest(id, request); // 기존 도서 조회 Book existingBook = findById(id..
`@OneToOne` 연관관계에서 `FetchType.LAZY`를 설정했지만 실제로는 Eager Loading이 발생하는 경우가 있다. @Entitypublic class Book { @Id @GeneratedValue private Long id; // 연관관계의 주인이 아닌 쪽 - Lazy Loading이 동작하지 않음 @OneToOne(mappedBy = "book", fetch = FetchType.LAZY) private Manuscript manuscript;}@Entitypublic class Manuscript { @Id @GeneratedValue private Long id; private byte[] file; ..
이번 글은 '스프링 MVC 2(타임리프 섹션) - 김영한' 강의를 듣는 중 식 기본 객체 (Expression Basic Objects) request = response = session = servletContext = locale = 편의 객체 Request Parameter = session = spring bean = 다음과 같은 코드에서 출발했다. 💡 타임리프 3.1+에서 `${#request}``${#response}``${#session}``${#servletContext}`코드들은 동작하지 않는다. SSR 렌더링인 타임리프에서 request나 session에 접근해 화면에 렌더링할 수 있는데, 개발자가 이런 코드를 실수로 작성한다면 ..
Spring 컨테이너에서 빈을 구성하는 방법은 3가지가 있다. 1. XML을 사용하여 직접 빈 선언 2. 클래스에 `@Bean` 애노테이션을 사용하여 빈 선언@Configurationpublic class AppConfig { @Bean public UserService userService() { return new UserServiceImpl(userRepository()); }}3. 컴포넌트 스캔을 사용하여 빈 선언@Componentpublic class UserServiceImpl implements UserService { // 구현 내용}최근 Spring에선 컴포넌트 스캔을 주로 사용하고 Spring boot는 컴포넌트 스캔을 기본으로 한다. 컴포넌트 스..
ID란?JPA의 `Repository` 인터페이스는 두 가지 타입 인자를 받는다. public interface Repository { }T: 도메인 엔티티ID: 도메인 클래스의 식별자(ID) 타입JPA에서 ID는 엔티티의 식별자이다. `Repository` 인터페이스에는 이 ID를 기반으로 동작하는 여러 메서드들이 존재한다.Optional findById(ID id);boolean existsById(ID id);void deleteById(ID id);이러한 메서드들은 엔티티의 ID를 참조해 DB 작업을 수행한다. ID는 데이터베이스의 기본 키와 매핑되어 엔티티의 고유성을 보장하고, 영속성 컨텍스트에서 엔티티를 식별하는 데 사용된다. ID는 다음과 같은 특성을 갖는다:Uniqueness: 동일한 타..
영속성 컨텍스트의 기능 중 Lazy loading, Eager loading에 대해 공부하다보니 N + 1문제를 접했다. JPA를 쓰다 보면 N + 1 문제를 흔히 접할 수 있고, 면접의 질문으로도 많이 나온다고 한다. N + 1문제에 대해 알아보자N + 1 문제란?N + 1문제는 엔티티의 연관관계를 통해 다른 엔티티를 조회할 때, 조회된 데이터 개수(N)만큼 연관관계의 조회 쿼리가 추가로 발생하는 현상이다. 예를 들어, 블로그 게시글과 댓글이 있는 경우, 게시글 목록을 조회한 후(1번의 쿼리) 각 게시글마다 댓글을 조회하기 위한 추가 쿼리(N번의 쿼리)가 발생할 수 있다. // 엔티티 관계 설정@Entitypublic class Post { @Id private Long id; priv..