티스토리 뷰

반응형

1.왜 JpaRepository를 상속하는가?

Spring Data JPA에서 제공하는 다양한 메소드를 사용하여 데이터 액세스 작업을 쉽게 구현하기 위해서

*JpaRepository 인터페이스란?

CRUD 작업과 검색 등의 데이터 액세스 작업을 추상화한 인터페이스

 

2. Repository 인터페이스가 JpaRepository를 상속하는 이유

Spring Data JPA에서 제공하는 다양한 메소드를 사용해, Post 엔티티에 대한 데이터 액세스 작업을 쉽게 구현하기 위해

Repository 인터페이스가 JpaRepository를 상속하면,

-> Post 엔티티에 대한 CRUD 작업과 검색 등의 데이터 액세스 작업을 수행할 수 있는 다양한 메소드를 사용

ex. findAll(), findById(), save(), delete()

 

3.JpaRepository를 상속할경우 RepositoryImpl(구현체)는 따로 없어도 되는 이유

JpaRepository 인터페이스를 상속받는 Repository 인터페이스를 정의하면, Spring Data JPA에서 자동으로 해당 Repository 인터페이스에 대한 구현체를 생성하여 사용

=> 별도의 구현체를 작성하지 않아도 JpaRepository 인터페이스를 상속받는 PostRepository 인터페이스를 정의하면, Spring Data JPA에서 자동으로 PostRepositoryImpl 클래스와 같은 구현체를 생성하여 사용

 

4. Builder패턴을사용하는 경우

*엔티티를 생성하거나 업데이트할 때 주로 사용

Spring Repository  -> Builder 패턴
데이터베이스와 상호작용을 하기 위해아래 메서드 제공
- 객체를 생성/저장/업데이트
다양한 방법으로 엔티티 객체를 생성/업데이트 가능

 

*Builder 패턴

객체를 생성할 때 필요한 파라미터들을 설정하는 메서드 체인을 사용하여 객체를 생성하는 방법

 

*사용방법

객체를 생성할 때 필요한 파라미터들을 일일이 지정x, 객체의 필드값을 변경하기 위해서도 메서드 체인을 사용

 

*장점

 - 가독성과 유지보수성

객체를 생성할 때 발생할 수 있는 오류 감소

public class User {
  private Long id;
  private String firstName;
  private String lastName;
  private String email;
  private LocalDate birthDate;
  // ... constructors, getters, and setters
}
public interface UserRepository extends JpaRepository<User, Long> {
  default User createUser(String firstName, String lastName, String email, LocalDate birthDate) {
    /*
     User 엔티티를 생성할 때 필요한 파라미터들을 일일이 지정하지 않아도 되며, 
     객체의 필드값을 변경하기 위해서도 메서드 체인을 사용하여 간편하게 처리할 수 있다.
    */
    
    return User.builder()
               .firstName(firstName)
               .lastName(lastName)
               .email(email)
               .birthDate(birthDate)
               .build();
  }
}

 

 

*Lombok의 Builder와 Java Design Pattern의 Builder Pattern의 차이

Builder(Lombok) : 클래스의 필드에 대한 생성자 + Setter메소드를 자동으로 생성

import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class User {
    private final String username;
    private final String password;
    private final String email;
    private final String phoneNumber;
}
// User 객체 생성
User user = User.builder()
            .username("chacha")
            .password("chacha123")
            .email("chacha@example.com")
            .phoneNumber("010-1234-5678")
            .build();

 

 

Builder(Java) : 객체생성을 단순화 하기위함

public class User {
    private final String username;
    private final String password;
    private final String email;
    private final String phoneNumber;
    private User(UserBuilder builder) {
        this.username = builder.username;
        this.password = builder.password;
        this.email = builder.email;
        this.phoneNumber = builder.phoneNumber;
    }
    public static class UserBuilder {
        private final String username;
        private final String password;
        private String email;
        private String phoneNumber;
        public UserBuilder(String username, String password) {
            this.username = username;
            this.password = password;
        }
        public UserBuilder email(String email) {
            this.email = email;
            return this;
        }
        public UserBuilder phoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
            return this;
        }
        public User build() {
            return new User(this);
        }
    }
}
// User 객체 생성
User user = new User.UserBuilder("john", "password123")
                .email("john@example.com")
                .phoneNumber("010-1234-5678")
                .build();

 

4.Update문에 @Modifying를 사용하는 이유

문제상황

더보기

Error creating bean with name 'foodRepository' defined in com.programmers.repository.FoodRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed; nested exception is org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract void com.programmers.repository.FoodRepository.update(com.programmers.domain.Food); Reason: Failed to create query for method public abstract void com.programmers.repository.FoodRepository.update(com.programmers.domain.Food)! No property 'update' found for type 'Food'; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract void com.programmers.repository.FoodRepository.update

 

문제이유

Food 엔티티 클래스에 'update'라는 속성이 없기 때문에 메소드를 생성할 수 없음

-> FoodRepositoryupdate 메소드가 정확하게 정의되지 않았음

-> @Modifying 주석을 사용하여 메소드가 데이터베이스를 수정한다는 것을 나타내야 함

 

@Modifying 이란?

  • JPA(Java Persistence API)에서 제공하는 EntityManager를 사용하여 엔티티를 수정하거나 삭제할 때 사용
  • 해당 쿼리가 업데이트나 삭제와 같은 수정 작업을 수행한다는 것을 명시적으로 표시할 수 있

@Modifiying 사용이유

  • 효율적인 쿼리 실행:  업데이트나 삭제와 같은 수정 작업을 수행하는 쿼리가 실행될 때 JPA에서 쿼리를 실행하는 방식을 최적화 -> 쿼리의 실행 속도가 향상
  • 예외 처리:  예외가 발생할 경우 해당 예외를 쉽게 처리할 수 있음
  • 가독성

@Modifiying 쓰는 방법

  1. 메소드 위에 @Modifying 애노테이션을 추가합니다.
  2. @Query 애노테이션을 사용하여 JPA 쿼리를 정의합니다.
  3. void를 반환하는 메소드를 정의합니다.
  4. EntityManager를 사용하여 쿼리를 실행합니다.

 

   @Transactional
    @Modifying
    @Query("UPDATE Food f SET f.name = :#{#food.name}, f.category = :#{#food.category}, f.price = :#{#food.price}, f.description = :#{#food.description}, f.image = :#{#food.image} WHERE f.id = :#{#food.id}")
    void update(@Param("food") Food food);

 

*EntityManager란?

Java Persistence API (JPA)에서 엔티티 객체를 관리하는 인터페이스

EntityManager는 데이터베이스에서 엔티티를 가져오거나 저장, 업데이트, 삭제하는 등의 작업을 수행할 수 있습니다. 또한 JPA 캐시를 관리하고, 트랜잭션을 관리하기도 합니다.

 

5.Update문에서 @Transactional를 사용하는 이유 

업데이트 작업이 트랜잭션 단위로 수행되어야 하기 때문

 

@Transactional이란?

트랜잭션 단위로 처리하기 위해 @Transactional 어노테이션을 사용

만약 업데이트 작업 중 일부가 실패하는 경우, 해당 트랜잭션은 롤백되어 이전 상태로 되돌린다.

-> 데이터 일관성을 유지

ex. 계좌에서 출금하는 작업/입금 작업이 모두 성공적으로 처리시에 전체 업무 완료.But 출금 작업 성공, 입금 작업 실패 일경우, 출금 작업을 롤백 -> 원래 상태로 되돌려야 함

 

트랜잭션단위란?

하나의 논리적인 작업을 모두 성공적으로 완료하거나 모두 취소하는 데이터베이스 작업의 최소 단위

ex.계좌 이체 : 출금, 입금

=> 두 개의 논리적인 작업이 모두 성공적으로 완료되어야 전체 "계좌 이체" 작업이 성공적으로 처리

=> 두 개의 논리적인 작업을 하나의 트랜잭션으로 묶어서 처리

*특징

  • 원자성 : 하나의 트랜잭션은 모두 성공하거나 모두 취소되어야 한다.
  • 일관성 : 트랜잭션의 실행 결과는 일관성 있게 유지되어야 한다.
  • 고립성 : 하나의 트랜잭션이 실행되는 동안에는 다른 트랜잭션에 해당 데이터 접근 불가
  • 지속성 : 트랜잭션이 성공적으로 완료된 후, 해당 데이터의 변경 내용은 영구적으로 저장되어야 함

 

6.Repository를 Interface로 자주 쓰는 이유

- Spring Data JPA와의 통합 

      Spring에서는 JPA(Java Persistence API)를 위한 Spring Data JPA 라이브러리를 제공

      Spring Data JPA는 JpaRepository 인터페이스를 제공하여, CRUD 메서드를 자동으로 생성할 수 있다.

      이를 통해, 개발자는 기본적인 CRUD 기능을 수행하는 메서드를 직접 작성할 필요가 없어진다.

- 유연한 확장성

      다른 데이터 소스나 저장소를 사용하는 경우에도 쉽게 변경할 수 있다.

      데이터베이스를 사용하지 않고 파일 시스템이나 메모리 데이터베이스를 사용하는 경우, 인터페이스를 구현하여 새로        운 데이터 소스를 지원할 수 있음.

- 테스트 용이성

인터페이스를 사용하면, 테스트 케이스에서 모의 객체(Mock Object)를 사용하여 데이터베이스나 외부 API에 의존하지 않고도, Repository를 테스트할 수 있다.

 

*다른 데이터 소스나 저장소를 사용하는 경우

- 성능 문제

대규모 데이터를 다루는 경우, 데이터베이스의 성능 문제가 발생할 수 있습니다. 이를 해결하기 위해, 데이터베이스 대신 메모리 데이터베이스나 캐시를 사용하는 경우가 있습니다.

 

- 데이터 분산

분산 데이터베이스를 사용하는 경우, 데이터베이스가 여러 지역에 분산되어 있을 수 있습니다. 이 경우, 지역에 따라 데이터 소스를 다르게 설정하여, 가장 가까운 데이터베이스를 사용할 수 있습니다.

 

7.만약 DB가 늦게와서 DB없이 작업을 해야한다면?

1.모의객체 사용

DB대신 가상의 객체를 사용하여 테스트하는 기법으로 Mockito를 사용한다.

 

*Mockito란?

데이터베이스 연결을 하고 이를 사용하여 데이터 검색/저장하는 경우 연결을 가상으로해서 직접 연결하지않고도 테스트 가능

1.모의 객체 생성 (인터페이스,클래스를 인수로 받아 객체 반환)

List<String? mockedList = mock(List.class);

2.모의 객체 동작 설정 (when으로 메소드 호출,호출시 반환값 지정)

when(mockedList.get(0)).thenReturn("first");

3.모의 객체 검증 verity()메서드로 동작 검증

verify(mockedList).add("one");

 

*Mockito와 MockMvc와의 차이는?

Mockito : 단위 테스트에서 사용, 모의객체를 생성/동작을 설정 => 객체를 격리,의존성 테스트

MockMvc : Spring 웹 Application을 테스트, HTTP 요청 및 응답을 테스트하고 컨트롤러 테스트하는데 도움

 

2.메모리내 데이터 베이스 사용

메모리내 데이터베이스를 사용하면 DB가 없을때도 DB와 유사한 동작을 수행할 수 있다.

다만 Application 실행중에만 데이터가 유지된다.

ex. H2

 

3.HashMap

1. hashmap으로 임시로 만든다.

  • key-value가 DB와 구조가 같기 때문      
private final Map<Long, Food> hashMap;

    public FoodMapRepository(HashMap<Long, Food> hashMap) {
        this.hashMap = hashMap;
    }
  • 이후 hashmap.put (=db의 save와 같음)등을 쓴다.
  • DB는 각 줄에 있는 데이터들을 모아 하나의 객체(=테이블)을 만들어서 Backend에 준다.(=json)

 

2.DB 나중에 오면 다 바꿔야 하나요?

-> repository에서 interface를 자주 쓴다.

8.Update대신 Save를 이용하면 된다.

*바꾸기 전

FoodRepository.class 

@Transactional
@Modifying
@Query("UPDATE Food f SET f.name = :#{#food.name}, f.category = :#{#food.category}, f.price = :#{#food.price}, f.description = :#{#food.description}, f.image = :#{#food.image} WHERE f.id = :#{#food.id}")
void update(@Param("food") Food food);

 

FoodService.class

*바꾼 후

FoodRepository.class -> 삭제

FoodService.class

public void update(long id, FoodRequestDto foodRequestDto) {
    foodRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다."));
    foodRepository.save(foodRequestDto.toEntity());
}

 

9.Repository 인터페이스의 특정 메서드에서 사용하는 속성 이름이 엔티티 클래스의 실제 속성 이름과 일치하지 않는 문제

*바꾸기 전

public interface ReviewRepository extends JpaRepository<Review, Long> {
    Optional<Review> findById(@Nullable Long reviewId);
}
@Getter
@Setter
@NoArgsConstructor
@Entity
@DynamicUpdate
public class Review {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long reviewId;
}

 

*바꾼 후 

public interface ReviewRepository extends JpaRepository<Review, Long> {
    Optional<Review> findByReviewId(@Nullable Long reviewId);
    Optional<Review> findStoreByReviewId(@Nullable Long reviewId); //메뉴 테이블에 있는데 findByStore하는게 맞는가?
}

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함