Spring Data JPA 시작하고 테스트해보기

Coastby·2023년 5월 23일
1

Sprint Data JPA 시작하기

Spring Boot 빌드하기

  1. Intellij - New Project
  2. Spring Initializr에서 아래와 같이 name, type, group 입력, JDK, java 버전 설정
  1. 의존성 추가하기

DB 연동하기

  1. src - main - resources - application.properties 파일에서 Rename (shift + F6)으로 들어가서 application.yml로 이름 변경

  2. JPA, DB 설정 추가하기

    spring:
     datasource:
       driver-class-name: com.mysql.cj.jdbc.Driver
       url: jdbc:mysql://localhost:3306/[schema이름]
       username: [username]
       password: [비밀번호]
     #jpa
     jpa:
       hibernate:
         ddl-auto: update
       show-sql: true
       properties:
         hibernate:
           format_sql: true  #sql format 적용

    환경변수로 Envrionment Variable을 통해 진짜 url, password를 넘겨줍니다.

    SPRING_DATASOURCE_URL=jdbc:mysql://[ec2주소]:3306
    SPRING_DATASOURCE_PASSWORD=[비밀번호]

application.yml의 JPA설정 값

  1. ddl-auto (Data Definition Language Auto)
    • 이 옵션은 데이터베이스 스키마의 생성 및 변경을 자동화하는 데 사용됩니다.
    • 주요 옵션 값:
      • create: 애플리케이션 시작 시 기존 스키마를 삭제하고 새로운 스키마를 생성합니다.
      • create-drop: 애플리케이션 시작 시 기존 스키마를 삭제하고 애플리케이션 종료 시에도 스키마를 삭제합니다.
      • update: 애플리케이션 시작 시 기존 스키마를 보존하면서 엔티티 클래스 변경 사항을 반영하여 스키마를 업데이트합니다.
      • validate: 애플리케이션 시작 시 기존 스키마를 검증하여 엔티티 클래스와 일치하지 않는 경우 경고 또는 예외를 발생시킵니다.
      • none: 자동 생성 및 변경을 비활성화하고, 기존 스키마를 유지합니다.
  2. show-sql
    • 이 옵션은 JPA가 생성한 SQL 쿼리를 콘솔에 출력할 지 여부를 설정합니다.
    • true로 설정하면 JPA가 실행하는 SQL 쿼리가 콘솔에 출력되며, false로 설정하면 출력되지 않습니다.
  3. format_sql
    • 이 옵션은 JPA가 생성한 SQL 쿼리를 가독성 있게 포맷할 지 여부를 설정합니다.
    • true로 설정하면 JPA가 SQL 쿼리를 보기 좋게 정렬 및 들여쓰기합니다.
    • false로 설정하면 SQL 쿼리가 원래의 형식을 유지합니다.

JPA 시작하기

UserEntity만들기

root 디렉토리에 entity 패키지에 생성 - User 클래스 생성

@Entity
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(unique = true, nullable = false)
  private String userName;
  @Column(nullable = false)
  private String password;

  public User() {
  }

  public User(String userName, String password) {
     this.userName = userName;
     this.password = password;
  }

  public Long getId() {
     return id;
  }

  public String getUserName() {
     return userName;
  }

  public String getPassword() {
     return password;
  }
}

기본키를 Long 타입으로 사용하는 이유

  1. null을 사용할 수 있다.

원시 타입(long)은 기본값이 0으로, 실제 값과 초기값이 없을 때와 판별하기 어렵습니다.Wrapper 클래스는 값이 없을 경우 null로 초기화되기 때문에 실제 값이 없는지 알 수 있습니다.

  1. int보다 범위가 크다.

시간이 지남에 따라 데이터가 쌓이면 id값이 증가할 경우를 대비할 수 있습니다.

JPA 주요 어노테이션

  • @Entity
    • @Entity 어노테이션은 JPA 엔티티 클래스를 표시하는 데 사용됩니다.
    • 데이터베이스의 테이블과 매핑되는 객체를 정의할 때 사용됩니다.
  • @Table
    • @Table 어노테이션은 엔티티 클래스가 매핑될 데이터베이스 테이블의 이름을 지정하는 데 사용됩니다.
    • 기본적으로 엔티티 클래스의 이름과 동일한 테이블 이름을 가지지만, 다른 테이블 이름으로 매핑하고자 할 때 사용할 수 있습니다.
  • @Id
    • @Id 어노테이션은 엔티티 클래스의 기본 키를 나타내는 필드를 표시하는 데 사용됩니다.
    • 데이터베이스의 기본 키와 매핑되며, 유일한 식별자 역할을 수행합니다.
  • @GeneratedValue
    • @GeneratedValue 어노테이션은 엔티티 클래스의 기본 키 값을 자동으로 생성하는 데 사용됩니다.
    • GenerationType.IDENTITY: 기본 키 값은 데이터베이스의 식별자 열을 사용하여 생성됩니다.
    • GenerationType.AUTO: 영속성 제공자가 데이터베이스와 구성에 기반하여 적절한 생성 전략을 선택합니다.
    • GenerationType.SEQUENCE: 기본 키 값은 데이터베이스 시퀀스를 사용하여 생성됩니다.
    • GenerationType.TABLE: 기본 키 값은 별도의 테이블을 사용하여 다음 사용 가능한 ID를 추적합니다.
  • @Column
    • @Column 어노테이션은 엔티티 클래스의 속성이 데이터베이스 테이블의 열과 매핑되는 방법을 정의하는 데 사용됩니다.
    • name: 데이터베이스 컬럼의 이름을 지정합니다. 기본적으로 필드 이름에서 컬럼 이름이 유도됩니다.
    • nullable: 컬럼이 null 값을 허용하는지 여부를 지정합니다.
    • length: 문자열 기반 필드의 컬럼 길이를 지정합니다.
    • precision과 scale: 숫자 필드의 정밀도와 스케일을 지정합니다.
    • unique: 컬럼 값이 고유해야 하는지 여부를 지정합니다.
    • columnDefinition: 컬럼 정의에 대한 사용자 정의 SQL 조각을 지정할 수 있습니다.
  • @OneToMany / @ManyToOne
    • @OneToMany와 @ManyToOne 어노테이션은 엔티티 클래스 간의 일대다 관계를 표시하는 데 사용됩니다.
    • @OneToMany 어노테이션은 한 엔티티가 여러 개의 다른 엔티티와 관계를 가지는 경우 사용되며, @ManyToOne 어노테이션은 다른 엔티티에 대한 매핑을 정의할 때 사용됩니다.

Lombok 적용하기

User entity에서 get 메소드, 생성자들을 삭제하고, Lombok 어노테이션을 적용합니다.

@Builder 어노테이션을 사용하기 위해서는 전체 필드를 포함하는 생성자가 필요하므로, @AllArgsConstructor도 추가해줍니다.

@Entity
@NoArgsConstructor
@Getter
@Builder
@AllArgsConstructor
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(unique = true, nullable = false)
  private String userName;
  @Column(nullable = false)
  private String password;

}

Lombok 주요 어노테이션

  • @Getter:
    • @Getter 어노테이션은 클래스의 필드에 대한 Getter 메서드를 자동으로 생성해줍니다.
    • 해당 필드에 대한 public Getter 메서드를 자동으로 생성하여 필드 값을 조회할 수 있게 합니다.
    • @Getter 어노테이션은 클래스 레벨이나 필드 레벨에 적용할 수 있습니다.
  • @NoArgsConstructor:
    • @NoArgsConstructor 어노테이션은 매개변수가 없는 기본 생성자를 자동으로 생성해줍니다.
    • 기본 생성자를 자동으로 생성하므로 객체를 인스턴스화할 때 매개변수 없이 생성자를 호출할 수 있습니다.
    • @NoArgsConstructor 어노테이션은 클래스 레벨에 적용할 수 있습니다.
  • @AllArgsConstructor:
    • @AllArgsConstructor 어노테이션은 모든 필드를 매개변수로 받는 생성자를 자동으로 생성해줍니다.
    • 클래스의 모든 필드를 포함한 생성자를 자동으로 생성하므로, 인스턴스를 생성하고 모든 필드 값을 한 번에 설정할 수 있습니다.
    • @AllArgsConstructor 어노테이션은 클래스 레벨에 적용할 수 있습니다.
  • @ToString:
    • @ToString 어노테이션은 toString() 메서드를 자동으로 생성해줍니다.
    • toString() 메서드는 객체의 문자열 표현을 반환하는데 사용되며, 해당 어노테이션을 사용하면 필드 값들을 포함한 문자열 표현을 간단하게 생성할 수 있습니다.
    • @ToString 어노테이션은 클래스 레벨에 적용할 수 있습니다.

Builder pattern, @Builder

Builder 패턴은 객체 생성을 유연하게 하기 위한 디자인 패턴 중 하나입니다. 일반적으로 많은 매개변수를 가진 객체를 생성할 때, 가독성을 높이고 코드 작성을 간편하게 하기 위해 사용됩니다.

Builder 패턴은 다음과 같은 특징을 가지고 있습니다:

  1. 매개변수를 가진 생성자 대신 메서드 체이닝 형태로 객체를 생성합니다.
  2. 각 매개변수는 Setter 메서드 체인을 통해 설정됩니다.
  3. 객체 생성 시점에 필요한 필드만 설정할 수 있으며, 선택적인 필드를 포함할 수 있습니다.
  4. 가독성을 높이고 매개변수의 순서를 기억할 필요가 없으므로 실수를 줄일 수 있습니다.
  5. 불변성(Immutability)을 유지할 수 있습니다.

@Builder 어노테이션은 Lombok에서 제공하는 어노테이션 중 하나로, Builder 패턴을 자동으로 생성해주는 기능을 제공합니다. @Builder 어노테이션을 클래스에 적용하면 해당 클래스에 대한 Builder 클래스가 자동으로 생성되고, 필드 값의 설정을 간편하게 할 수 있습니다.

DB에 Table생성 되었는지 확인하기

intellij의 DB 또는 workbench를 통해서 entity의 테이블이 설정대로 만들어졌는지 확인

Repository만들기

리포지토리를 생성하기 위해서는 접근하려는 테이블과 매핑되는 엔티티에 대한 인터페이스를 생성하고, JpaRepository를 상속받으면 됩니다.

JpaRepository를 상속받을 때는 대상 엔티티(User)와 해당 엔티티의 기본값 타입 (Long)을 지정해야 합니다.

생성된 리포지토리는 JpaRepository를 상속받으면서 별도의 메서드 구현 없이도 많은 기능을 제공합니다. 일반적으로 CRUD에서 따로 생성해서 사용하는 메서드는 Read에 해당하는 SELECT 쿼리밖에 없습니다.

  1. root 디렉토리 - repository 패키지 생성 - UserRepository 인터페이스 생성
import com.springboot.jpa.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

TestCode에서 실행하기

  1. UserRepositoryTest 생성하기

    UserRepositoy에서 cmd + N - Test.. 탭 - OK 클릭

    src - test - root 디렉토리 - repository 패키지 안에 테스트 클래스에 어노테이션 추가

    import com.springboot.jpa.entity.User;
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
    import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
    import org.springframework.test.annotation.Rollback;
    import org.springframework.test.context.ActiveProfiles;
    
    @DataJpaTest
    @ActiveProfiles("test")
    @Rollback(value = false)
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    class UserRepositoryTest {
      @Autowired
      private UserRepository userRepository;
    
    }

📌 Annotations

  • @DataJpaTest:
    • @DataJpaTest 어노테이션은 Spring Data JPA 테스트를 위해 사용됩니다.
    • 이 어노테이션을 사용하면 테스트에 필요한 JPA 관련 구성만 로드되며, 임베디드 데이터베이스가 사용됩니다.
    • 실제 데이터베이스 연결 없이 JPA 리포지토리를 테스트할 수 있습니다.
  • @ActiveProfiles("test"):
    • @ActiveProfiles 어노테이션은 특정 프로파일을 활성화하는 데 사용됩니다.
    • test 프로파일을 활성화하여 테스트 환경에서 사용할 프로파일을 설정할 수 있습니다.
    • 프로파일에 따라 다른 구성이 활성화되고 동작합니다.
  • @Rollback(value = false):
    • @Rollback 어노테이션은 테스트 메서드가 롤백을 수행할지 여부를 지정합니다.
    • 기본적으로 @DataJpaTest에서는 @Transactionl 어노테이션이 포함되어 있어 테스트가 마칠 때마다 자동으로 Rollback한다.
    • value 속성을 false로 설정하면 롤백이 수행되지 않으며, 데이터베이스의 변경 사항이 유지됩니다.
  • @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE):
    • @AutoConfigureTestDatabase 어노테이션은 테스트용 데이터베이스를 자동으로 구성하는 데 사용됩니다.
    • replace 속성을 AutoConfigureTestDatabase.Replace.NONE로 설정하면 기존의 데이터베이스 구성을 변경하지 않고 사용합니다.
    • @DataJpaTest를 선언하면 자동으로 EmbeddedDatabase를 사용하게 된다. 따라서 현재 사용하는 db인 mysql로 테스트를 하려고 하면 @AutoConfigureTestDatabase(replace = Replace.NONE)을 이용해서 자동 설정되지 않게 한다.
    • 따라서 실제 데이터베이스와 통합된 테스트를 수행할 수 있습니다.
  1. application-test.yml 파일 생성
spring:
 jpa:
   show-sql: true
   hibernate:
     ddl-auto: create
   defer-datasource-initialization: true
 datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql://localhost:3306/[스키마 이름]
   username: root
   password: password!

INSERT

아래 테스트 실행 후 DB에 데이터 생성 확인

@Test
@DisplayName("유저 등록")
void 유저등록() {
  User user = userRepository.save(User.builder().userName("이름").password("비밀번호").build());
}

UPDATE

@Test
@DisplayName("유저 수정")
void 유저수정() {
  //유저 등록
  User saved = userRepository.save(User.builder().userName("이름").password("비밀번호").build());
  assertEquals("이름", saved.getUserName());
  //유저 이름 수정
  User updated = userRepository.save(User.builder().id(saved.getId()).userName("수정된이름").password(saved.getPassword()).build());
  assertEquals("수정된이름", updated.getUserName());
}

DELETE

@Test
@DisplayName("유저 삭제")
void 유저삭제() {
  //유저 등록
  User saved = userRepository.save(User.builder().userName("이름").password("비밀번호").build());
  assertEquals("이름", saved.getUserName());
  //유저 삭제
  userRepository.deleteById(saved.getId());
}

SELECT

@Test
@DisplayName("유저 아이디로 찾기")
void 유저아이디로검색() {
  //유저 등록
  User saved = userRepository.save(User.builder().userName("이름").password("비밀번호").build());
  assertEquals("이름", saved.getUserName());
  //유저 아이디로 검색
  Optional<User> optionalUser = userRepository.findById(saved.getId());
  assertEquals("이름", optionalUser.get().getUserName());

  //유저 삭제 후 검색
  userRepository.deleteById(saved.getId());
  Optional<User> deletedUser = userRepository.findById(saved.getId());
  assertTrue(deletedUser.isEmpty());

}

SELECT ALL

@Test
@DisplayName("전체 유저 검색")
void 유저전체검색() {
  //유저 등록
  User saved1 = userRepository.save(User.builder().userName("이름1").password("비밀번호").build());
  User saved2 = userRepository.save(User.builder().userName("이름2").password("비밀번호").build());
  User saved3 = userRepository.save(User.builder().userName("이름3").password("비밀번호").build());
  User saved4 = userRepository.save(User.builder().userName("이름4").password("비밀번호").build());
  User saved5 = userRepository.save(User.builder().userName("이름5").password("비밀번호").build());
  //유저 아이디로 검색
  List<User> users = userRepository.findAll();
  assertEquals(5, users.size());

  //유저 삭제 후 검색
  userRepository.deleteById(saved1.getId());
  userRepository.deleteById(saved2.getId());
  List<User> usersAfter = userRepository.findAll();
  assertEquals(3, usersAfter.size());

}

📌 JPA에서 기본으로 제공하는 메서드(참고)

@NoRepositoryBean
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

  void flush();

  <S extends T> S saveAndFlush(S entity);<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

  @Deprecated
  default void deleteInBatch(Iterable<T> entities) {
     deleteAllInBatch(entities);
  }

  void deleteAllInBatch(Iterable<T> entities);

  void deleteAllByIdInBatch(Iterable<ID> ids);

  void deleteAllInBatch();

  @Deprecated
  T getOne(ID id);

  @Deprecated
  T getById(ID id);

  T getReferenceById(ID id);

  <S extends T> List<S> findAll(Example<S> example);

  @Override
  <S extends T> List<S> findAll(Example<S> example, Sort sort);

JPA 메서드 생성

JpaRepository 인터페이스는 일반적인 CRUD(Creating, Reading, Updating, Deleting) 작업을 수행하기 위한 메서드를 자동으로 생성해주는 기능을 제공합니다. JpaRepository를 상속한 인터페이스를 정의하면, 해당 인터페이스의 메서드들은 자동으로 JPA 쿼리로 변환되어 데이터베이스에 접근할 수 있습니다.

  1. 메서드 이름
    • 메서드 이름은 일정한 규칙을 따라야 합니다: findBy, readBy, getBy, queryBy, searchBy, countBy, deleteBy 등의 접두사를 사용합니다.
    • 이후에는 엔티티 클래스의 속성 이름을 사용하여 검색 조건을 지정합니다. 대소문자를 구분하며, 뒤에 And, Or, OrderBy 등의 키워드를 사용하여 조건을 연결할 수 있습니다.
    • 예를 들어, findByFirstName(String firstName) 메서드는 firstName 속성이 주어진 값과 일치하는 엔티티를 조회하는 쿼리를 생성합니다.
  2. 반환 타입
    • 조회 메서드의 반환 타입은 다음과 같이 지정할 수 있습니다:
      • 단일 엔티티: Entity, Optional
      • 여러 엔티티: List, Set, Page
  3. 파라미터
    • 메서드의 파라미터는 조회 조건을 지정하는 데 사용됩니다.
    • 기본적으로는 단일 값이나 여러 값들을 받을 수 있습니다.
    • 여러 조건을 연결할 때는 And, Or 키워드를 사용하여 메서드 시그니처를 작성할 수 있습니다.
  4. 정렬
    • 정렬을 지정하기 위해 메서드 이름에 OrderBy 키워드를 사용합니다.
    • 정렬 기준으로 사용할 속성 이름을 추가하고, Asc 또는 Desc 키워드를 사용하여 오름차순 또는 내림차순을 지정합니다.
    • 예를 들어, findByFirstNameOrderByLastNameAsc(String firstName) 메서드는 firstName이 주어진 값과 일치하는 엔티티를 lastName을 오름차순으로 정렬하여 조회합니다.
profile
훈이야 화이팅

0개의 댓글