Spring Data JPA #3 Spring Data JPA를 위한 프로젝트 생성 #2

2021. 10. 1. 16:06JAVA/Spring

본 글은 스타트 스프링 부트 도서의 내용을 복습하기 위해 작성된 글입니다.

3. JPA 처리를 담당하는 Repository 인터페이스 설계하기

Spring Data JPA를 이용하는 경우에는 별도의 클래스 파일을 작성하지 않고 원하는 인터페이스를 구현하는 것만으로도 JPA와 관련된 모든 처리가 끝나게 됩니다. 일반적으로 과걱에는 DAO라는 개념을 이용했듯이, JPA를 이용하는 경우에는 Repository라는 용어로 칭합니다.

 

Spring Data JPA에서는 다음과 같은 인터페이스 구조를 사용하고 있습니다.

모든 인터페이스가 <T, ID> 두 개의 제네릭 타입을 사용하는 것을 볼 수 있는데, T는 엔티티의 타입 클래스를, ID는 식별자(PK)의 타입을 의미합니다. 이때 ID에 해당하는 타입은 반드시 java.io.Serializable 인터페이스 타입이어야만 합니다.

 

가장 상위의 Repository 인터페이스는 사실상 아무 기능이 없기 때문에 실제 주로 사용하는 것은 CRUD(Create, Read, Update, Delete) 작업을 위주로 하는 CrudRepository 인터페이스나 페이징 처리, 검색 처리 등을 할 수 있는 PagingAndSortingRepository 인터페이스입니다.

 

3.1 Repository 인터페이스 설계

'org.zerock.persistence' 패키지 추가후 BoardRepository 인터페이스 생성

package org.zerock.persistence;

import org.springframework.data.repository.CrudRepository;
import org.zerock.domain.Board;

public interface BoardRepository extends CrudRepository<Board, Long>{
	
}

CrudRepository 인터페이스의 메서드는 아래 주소에서 확인이 가능합니다.

https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html

 

CrudRepository (Spring Data Core 2.5.5 API)

Returns all instances of the type T with the given IDs. If some or all ids are not found, no entities are returned for these IDs. Note that the order of elements in the result is not guaranteed.

docs.spring.io

 

3.2 작성한 엔티티 테스트

'src/test/java/zerock' 패키지에 BoardRepositoryTests 테스트 클래스 생성

package org.zerock;

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class BoardRepositoryTests {
	
}

 

3.3 등록 작업 테스트 (save)

코드에서 상속한 CrudRepository 인터페이스는 save()라는 메서드를 이용해서 데이터베이스에 엔티티의 정보를 추가하거나 수정하는 작업을 수행합니다. 동일한 메소드(save)로 insert와 update 작업도 이루어지는데 이 부분을 이해하기 위해서는 엔티티 매니저의 존재를 인식할 필요가 있습니다.

 

Repository 쪽에서 save()라는 메서드가 호출되면, 내부에서는 엔티티 매니저가 영속 컨텍스트에 해당 식별키를 가진 엔티티가 존재하는지를 확인하게 됩니다. 만일 동일한 식별 데이터를 가지는 엔티티가 없다면 엔티티 매니저는 이를 영속 컨텍스트에 저장하고, 데이터베이스에도 추가하게 됩니다. 반면에 식별 데이터를 가지는 엔티티가 이미 존재한다면 메모리에 보관되는 엔티리를 수정하는 작업과 데이터베이스를 갱신(update)하는 작업을 진행합니다.

 

	@Test
	public void testInsert() {
		Board board = new Board();
		board.setTitle("게시글의 제목");
		board.setContent("게시물 내용 넣기....");
		board.setWriter("user00");
		
		boardRepo.save(board);
	}

 

Output

... 생략
Hibernate: drop table if exists hibernate_sequence
Hibernate: drop table if exists tbl_boards
Hibernate: create table hibernate_sequence (next_val bigint) engine=InnoDB
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: create table tbl_boards (bno bigint not null, content varchar(255), regdate datetime, title varchar(255), updatedate datetime, writer varchar(255), primary key (bno)) engine=InnoDB
2021-10-01 15:42:45.169  INFO 23212 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-10-01 15:42:45.187  INFO 23212 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-10-01 15:42:45.627  WARN 23212 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-10-01 15:42:47.370  INFO 23212 --- [           main] org.zerock.BoardRepositoryTests          : Started BoardRepositoryTests in 9.682 seconds (JVM running for 11.672)
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: insert into tbl_boards (content, regdate, title, updatedate, writer, bno) values (?, ?, ?, ?, ?, ?)
... 생략

위의 그림을 보면 application.properties 파일의 spring.jpa.hibernate.ddl-auto=create로 설정하면 기존 테이블을 삭제하고 다시 생성하여 삽입을 테스트한 결과입니다. 만약 기존 테이블이 존재하면 삭제하지 않도록 변경하려면 spring.jpa.hibernate.ddl-auto=update로 변경하면 됩니다.

 

3.4 조회 작업 테스트 (findByID)

조회 테스트의 코드는 findByID()이라는 메서드를 이용합니다. findByID()은 파라미터로 식별 데이터를 사용합니다.

	@Test
	public void testRead() {
		Optional<Board> board = boardRepo.findById(1L);
		System.out.println(board);
	}
Output 일부

Hibernate: select board0_.bno as bno1_0_0_, board0_.content as content2_0_0_, board0_.regdate as regdate3_0_0_, board0_.title as title4_0_0_, board0_.updatedate as updateda5_0_0_, board0_.writer as writer6_0_0_ from tbl_boards board0_ where board0_.bno=?
Optional[Board(bno=1, title=게시글의 제목, writer=user00, content=게시물 내용 넣기...., regdate=2021-10-01 15:42:48.0, updatedate=2021-10-01 15:42:48.0)]

 

3.5 수정 작업 테스트 (save)

수정 작업은 등록 작업과 동일하게 save() 메서드를 호출합니다.

Output 일부

Read First....................
Hibernate: select board0_.bno as bno1_0_0_, board0_.content as content2_0_0_, board0_.regdate as regdate3_0_0_, board0_.title as title4_0_0_, board0_.updatedate as updateda5_0_0_, board0_.writer as writer6_0_0_ from tbl_boards board0_ where board0_.bno=?
Update Title..................
Call Save()...................
Hibernate: select board0_.bno as bno1_0_0_, board0_.content as content2_0_0_, board0_.regdate as regdate3_0_0_, board0_.title as title4_0_0_, board0_.updatedate as updateda5_0_0_, board0_.writer as writer6_0_0_ from tbl_boards board0_ where board0_.bno=?
Hibernate: update tbl_boards set content=?, regdate=?, title=?, updatedate=?, writer=? where bno=?

 

코드를 실행하면 'Read First...' 부분이 실행됩니다. '1'번 게시물을 읽어 들이는 과정이 필요하기 때문에 데이터베이스에서 select 문이 동작하면서 데이터를 읽어냅니다.

 

데이터베이스에서 조회했기 때문에 board 변수가 참조하는 객체는 식별 데이터를 가진 상태가 됩니다.

 

'Call Save()...' 이하 부분을 보면 'select'문과 'update'가 실행되는 것을 볼 수 있습니다.

 

이처럼 JPA는 데이터베이스에 바로 작업을 하는 JDBC와 달리 스스로 엔티티 객체들을 메모리상에서 관리하고, 필요한 경우에 데이터베이스에 작업을 하게 됩니다. 따라서 수정과 삭제 작업은 직접 데이터베이스에 바로 SQL문을 실행하는 것이 아니라, 엔티티 객체가 우선적으로 메모리상에 존재하고 있어야 합니다. 이 과정을 위해서 'select'가 동작하게 되는 것입니다.

 

테스트 코드는 매번 새롭게 실행되기 때문에 관리되고 있는 엔티티 객체가 없어서 현재 작성하는 수정과 삭제 작업에는 'select'가 우선으로 실행됩니다. 만일 이때 조회한 엔티티와 수정한 엔티티가 동일한 값들을 가지고 있어서 수정할 필요가 없다면 'update'문은 실행하지 않습니다.

Output 일부

Read First....................
Hibernate: select board0_.bno as bno1_0_0_, board0_.content as content2_0_0_, board0_.regdate as regdate3_0_0_, board0_.title as title4_0_0_, board0_.updatedate as updateda5_0_0_, board0_.writer as writer6_0_0_ from tbl_boards board0_ where board0_.bno=?
Update Title..................
Call Save()...................
Hibernate: select board0_.bno as bno1_0_0_, board0_.content as content2_0_0_, board0_.regdate as regdate3_0_0_, board0_.title as title4_0_0_, board0_.updatedate as updateda5_0_0_, board0_.writer as writer6_0_0_ from tbl_boards board0_ where board0_.bno=?

 

3.5 수정 작업 테스트 (deleteById)

Spring Data JPA에서는 deleteById()을 이용해서 삭제 처리를 합니다.

	@Test
	public void testDelete() {
		System.out.println("DELETE Entity ");
		boardRepo.deleteById(1L);
	}
Output 일부

DELETE Entity 
Hibernate: select board0_.bno as bno1_0_0_, board0_.content as content2_0_0_, board0_.regdate as regdate3_0_0_, board0_.title as title4_0_0_, board0_.updatedate as updateda5_0_0_, board0_.writer as writer6_0_0_ from tbl_boards board0_ where board0_.bno=?
Hibernate: delete from tbl_boards where bno=?

삭제 작업 테스트 결과를 보면 수정과 마찬가지로 삭제하기 이전에 엔티티 객체가 관리되지 않았기 때문에 우선 'select'를 통해서 엔티티 객체를 보관하고 이후에 'deleteById' 가 실행되는 것을 확인할 수 있습니다.

 

References

스타트 스프링 부트, 구멍가게 코딩단 저