[ JPA ] 1. Repository interface 메소드
백엔드/JPA

[ JPA ] 1. Repository interface 메소드

1. 사전 준비

-프로젝트 구성

 

-build.gradle

dependencies{
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

1. data.sql을 통해 기초 데이터 생성

call next value for hibernate_sequence;

→ insert를 하는 경우 id를 자동 생성하는데 그것이 충돌나지 않기 위하여 id값을 증가

call next value for hibernate_sequence;
insert into user (`id`, `name`, `email`, `created_at`, `updated_at`) values (1, 'martin', 'martin@fastcampus.com', now(), now());

call next value for hibernate_sequence;
insert into user (`id`, `name`, `email`, `created_at`, `updated_at`) values (2, 'dennis', 'dennis@fastcampus.com', now(), now());

call next value for hibernate_sequence;
insert into user (`id`, `name`, `email`, `created_at`, `updated_at`) values (3, 'sophia', 'sophia@slowcampus.com', now(), now());

call next value for hibernate_sequence;
insert into user (`id`, `name`, `email`, `created_at`, `updated_at`) values (4, 'james', 'james@slowcampus.com', now(), now());

call next value for hibernate_sequence;
insert into user (`id`, `name`, `email`, `created_at`, `updated_at`) values (5, 'marti
n', 'martin@another.com', now(), now());

2. application.yml 설정

  • spring.h2.console.enabled: true h2 console을 사용하는지 여부 설정
  • spring.jpa.defer-datasource-initialization: truedata.sql을 시스템이 올라온 후 사용할지 여부 설정
  • spring.jpa.show-sql: truejpa 쿼리를 볼 것인지 여부 설정
  • spring.jpa.properties.hibernate.format_sql: truejpa 쿼리를 정렬된 상태로 볼 수 있는 설정
  • logging.level.org.hibernate.type: trace파라미터 값이 어떻게 매핑되는지 확인하는 설정
spring:
  h2:
    console:
      enabled: true
  jpa:
    defer-datasource-initialization: true
    show-sql: true
    properties:
      hibernate:
        format_sql: true

logging:
  level:
    org.hibernate.type: trace

3. domain > User.java 작성

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

4. repository > UserRepository.java작성

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

2. 테스트 코드 작성 및 실행 결과

crud() 메소드에서 학습용 코드를 넣어서 결과를 확인

@SpringBootTest //스프링 컨텍스트를 사용하겠다.
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void crud(){ //create, read, update, delete
		}
}

 

[ Select 다중 쿼리 ]

→ 공통: users.stream().forEach(System.out::println); 이용하여 결과 출력

  • List<T> findAllById(Iterable<ID> ids);
List<User> users = userRepository.findAllById(Lists.newArrayList(1L, 3L, 5L));
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.name as name4_0_,
        user0_.updated_at as updated_5_0_ 
    from
        user user0_ 
    where
        user0_.id in (
            ? , ? , ?
        )
  • List<T> findAll(Sort sort);
List<User> users = userRepository.findAll(Sort.by(Sort.Direction.DESC, "name"));
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.name as name4_0_,
        user0_.updated_at as updated_5_0_ 
    from
        user user0_ 
    order by
        user0_.name desc

 

[ Select 단일 쿼리 ]

  • T getOne(ID id);

*Lazy(지연된, 로드된 후 실행)한 조회방법으로 테스트 코드에 @Transactional 필요하다.

@Test
@Transactional
void crud(){
		User user = userRepository.getOne(1L); //getOne Entity에 대해서 lazy한 로딩
    System.out.println(user);
}
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.created_at as created_2_0_0_,
        user0_.email as email3_0_0_,
        user0_.name as name4_0_0_,
        user0_.updated_at as updated_5_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
  • Optional<T> findById(ID id);

→ 가장 일반적으로 사용하는 방식, Optional로 Entity를 감쌓다.

→ 쿼리는 getOne와 동일하지만, Lazy의 차이가 있음. @Transactional 필수적이지 않음

User user = userRepository.findById(1L).orElse(null);
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.created_at as created_2_0_0_,
        user0_.email as email3_0_0_,
        user0_.name as name4_0_0_,
        user0_.updated_at as updated_5_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
  • long count();
long count = userRepository.count();
Hibernate: 
    select
        count(*) as col_0_0_ 
    from
        user user0_
  • boolean existsById(ID id);
boolean exists = userRepository.existsById(1L);
Hibernate: 
    select
        count(*) as col_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?

 

[ Paging 쿼리 ]

  • Page<T> findAll(Pageable pageable);
Page<User> users = userRepository.findAll(PageRequest.of(0, 3)); //0페이지, 3개씩

System.out.println("page : " + users); //객체
System.out.println("totalElements : " + users.getTotalElements()); //총 레코드 수
System.out.println("totalPages : " + users.getTotalPages()); //총 페이지 수
System.out.println("numberOfElements : " + users.getNumberOfElements()); //현재 페이지의 레코드 수 (zero-base index 0에서부터 시작)
System.out.println("sort : " + users.getSort()); //소트 방식
System.out.println("size : " + users.getSize()); //페이징 나누는 수

users.getContent().forEach(System.out::println);
<쿼리>
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.name as name4_0_,
        user0_.updated_at as updated_5_0_ 
    from
        user user0_ limit ?
Hibernate: 
    select
        count(user0_.id) as col_0_0_ 
    from
        user user0_

<결과>
page : Page 1 of 2 containing com.example.bookmanager.domain.User instances
totalElements : 5
totalPages : 2
numberOfElements : 3
sort : UNSORTED
size : 3
User(id=1, name=martin, email=martin@fastcampus.com, createdAt=2021-07-20T21:58:38.005887, updatedAt=2021-07-20T21:58:38.005887)
User(id=2, name=dennis, email=dennis@fastcampus.com, createdAt=2021-07-20T21:58:38.016890, updatedAt=2021-07-20T21:58:38.016890)
User(id=3, name=sophia, email=sophia@slowcampus.com, createdAt=2021-07-20T21:58:38.017889, updatedAt=2021-07-20T21:58:38.017889)

 

[ Insert 쿼리 ]

  • <S extends T> S save(S entity);
userRepository.save(new User("Kim", "kim@naver.com"));
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        user
        (created_at, email, name, updated_at, id) 
    values
        (?, ?, ?, ?, ?)
  • <S extends T> S saveAndFlush(S entity);

→ flush: 쿼리 변화x 디비 반영시점을 조정, 동작확인 어렵다.

userRepository.saveAndFlush(new User("Kim", "kim@naver.com"));
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        user
        (created_at, email, name, updated_at, id) 
    values
        (?, ?, ?, ?, ?)

 

[ Delete 쿼리 ]

  • void delete(T entity);

→ 해당 데이터가 있는지 조회 후 삭제

userRepository.delete(userRepository.findById(1L).orElseThrow(RuntimeException::new));
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.created_at as created_2_0_0_,
        user0_.email as email3_0_0_,
        user0_.name as name4_0_0_,
        user0_.updated_at as updated_5_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    delete 
    from
        user 
    where
        id=?
  • void deleteById(ID id);
userRepository.deleteById(1L);
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.created_at as created_2_0_0_,
        user0_.email as email3_0_0_,
        user0_.name as name4_0_0_,
        user0_.updated_at as updated_5_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    delete 
    from
        user 
    where
        id=?
  • void deleteAll();

→ 데이터를 조회 후 한건 한건씩 삭제

userRepository.deleteAll();
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.name as name4_0_,
        user0_.updated_at as updated_5_0_ 
    from
        user user0_
Hibernate: 
    delete 
    from
        user 
    where
        id=?
Hibernate: 
    delete 
    from
        user 
    where
        id=?
...
  • void deleteAll(Iterable<? extends T> entities);
userRepository.deleteAll(userRepository.findAllById(Lists.newArrayList(1L, 3L)));
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.name as name4_0_,
        user0_.updated_at as updated_5_0_ 
    from
        user user0_ 
    where
        user0_.id in (
            ? , ?
        )
Hibernate: 
    delete 
    from
        user 
    where
        id=?
Hibernate: 
    delete 
    from
        user 
    where
        id=?
  • void deleteAllInBatch();

→ 조회하지 않고 바로 삭제

userRepository.deleteAllInBatch();
Hibernate: 
    delete 
    from
        user
  • void deleteAllInBatch(Iterable<T> entities);
userRepository.deleteAllInBatch(userRepository.findAllById(Lists.newArrayList(1L, 3L)));
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.name as name4_0_,
        user0_.updated_at as updated_5_0_ 
    from
        user user0_ 
    where
        user0_.id in (
            ? , ?
        )
Hibernate: 
    delete 
    from
        user 
    where
        id=? 
        or id=?

 

[ update 쿼리 ]

  • <S extends T> S save(S entity);

→ insert문과 동일한 save()를 사용 id값이 있는 entity인 경우 update를 한다.

userRepository.save(new User("david", "david@gmail.com")); //insert

User user = userRepository.findById(1L).orElseThrow(RuntimeException::new); //select
user.setEmail("martin-update@gmail.com");

userRepository.save(user); //update
//SimpleJpaRepository.java save 구현 부분
public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null.");

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

 

[ Example ] - 잘 사용하진 않음

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

withIgnorePaths("name") name의 설정은 무시하겠다.

withMatcher("email", endsWith()) email 값으로 끝나는 조건 ( like email '%fastcampus.com' )

withMatcher("email", startsWith()) email 값으로 시작하는 조건 ( like email 'fastcampus.com%' )

withMatcher("email", contains()) email 값을 모두 포함하는 조건

( like email '%fastcampus.com%' )

ExampleMatcher matcher = ExampleMatcher.matching()
        .withIgnorePaths("name")
        .withMatcher("email", endsWith());
Example<User> example = Example.of(new User("ma", "fastcampus.com"), matcher);

userRepository.findAll(example).forEach(System.out::println);
Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.name as name4_0_,
        user0_.updated_at as updated_5_0_ 
    from
        user user0_ 
    where
        user0_.email like ? escape ?
2021-07-20 22:27:26.326 TRACE 9200 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [%fastcampus.com]
2021-07-20 22:27:26.326 TRACE 9200 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [CHAR] - [\]

 

Example<User> example = Example.of(new User("ma", "fastcampus.com"));
userRepository.findAll(example).forEach(System.out::println);

 

User user = new User();
user.setEmail("slow");
ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("email", contains());
Example<User> example = Example.of(user, matcher);