본문 바로가기
Clone Coding/스프링 부트와 AWS

[Spring] JPA로 database 다루기

by 연로그 2021. 1. 11.
반응형

JPA를 사용하는 이유?

 우리 회사는 현재 MyBatis + SQL Mapper를 통해 db를 다룬다.

SQL을 다루는 시간이 꽤 길고 단순 반복 작업을 반복하게 되는데 '객체지향'과는 약간 거리가 있는 느낌이다.

JPA는 이런 SQL에 종속적인 개발을 피하기 위해 나온 것으로, Springboot + JPA 조합을 사용하는 기업들이 늘고 있다.

 

JPA란?

: 인터페이스로서의 자바 표준 명세서.

- 인터페이스인 JPA 사용을 위해 구현체가 필요한데 Hibernate, Eclipse Link 등이 있으나 Spring에서는 이를 직접 다루진 않는다. 구현체들을 좀 더 쉽게 사용하고자 추상화시킨 Spring Data JPA를 이용하는데, JPA<-Hibernate<-Spring Data JPA 순으로 접근된다. Spring Data JPA는 사용하는데 Hibernate와 큰 차이가 없지만, Hibernate 외 다른 구현체로 쉽게 교체할 수 있고, 관계형 데이터베이스 외의 다른 저장소로도 쉽게 교체할 수 있다.

- 단점: 러닝 커브가 높다.

( JPA 사용을 위해선 객체지향 프로그래밍과 관계형 데이터베이스를 둘 다 이해해야 하기 때문에)

 

JPA 적용하기

1) dependencies 추가하기

build.gradle의 dependencies에 db와 jpa 추가

compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('com.h2database:h2')

h2는 별도 설치 없이 의존성 만으로 db 관리가 가능해 추가해두었다.

이 프로젝트에선 간단한 수준만 다루므로 사용했다.

 

2) Entity 클래스 생성

Controller를 저장했던 web 폴더와 같은 위치에 domain 폴더를 생성한다.

기존 MyBatis에서 사용했던 dao 패키지와 유사하다.

xml에 쿼리를 담고, 클래스는 오로지 쿼리의 결과만 담던 일들이 모두 도메인 클래스에서 해결된다.

 

경로/domain/posts/Posts.java를 생성한다.

@Getter
@NoArgsConstructor
@Entity
public class Posts {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(length = 500, nullable =false)
	private String title;

	@Column(columnDefinition = "TEXT", nullable =false)
	private String content;
	
	private String author;
	
	@Builder
	public Posts(String title, String content, String author) {
		this.title = title;
		this.content = content;
		this.author = author;
	}
}

@Entity

- 테이블과 링크될 클래스

- 기본 값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍으로 테이블 이름 매칭

 

@Id

- 해당 테이블의 PK 필드

 

@GeneratedValue

- PK 생성 규칙

- 스프링 부트 2.0에서는 GenerationType.IDENTITY 옵션 추가 해야만 auto_increment가 된다.

- 가능하면 PK는 auto_increment를 사용하는 것이 좋다.

  주민번호 같은 비즈니스상 유니크 키나 복합키 등은 난감한 상황이 종종 발생

 

@Column

- 테이블의 컬럼

- 따로 선언하지 않아도 해당 클래스의 필드는 모두 컬럼.

  디폴트 외에 추가로 변경이 필요한 옵션이 있을 경우에 사용

- 문자열: VARCHAR(255) 기본

 

 

Posts 클래스는 실제 DB의 테이블과 매칭될 클래스. ( Entity 클래스라고도 함 )

Entity 클래스에서는 Setter 메소드를 만들지 않는다.

해당 클래스의 인스턴스 값들이 언제 어디서 변해야하는지 코드상으로 명확히 구분할 수 없어 차후 기능 변경 시 문제점을 발생시킬 수 있다. 필드 값 변경이 필요하다면 목적과 의도를 나타낼 수 있는 메소드를 추가해야 한다.

( ex: 주문 상태인 status를 set하는 함수 -> setStatus (x), cancelOrder (o) )

 

기본적으로 생성자를 통해 값을 채우고 DB에 삽입한다.

값 변경이 필요한 경우 해당 이벤트에 맞는 public 메소드를 호출해 변경한다.

해당 프로젝트에서는 생성자 대신 빌더를 이용함으로써 어느 필드에 어느 값을 넣어야하는지 더 명확하게 만들었다.

 

예를 들어 아래 코드에서는 Example(a,b)와 Example(b, a)의 결과가 다를 것이다.

public Example(String a,String b) {
	this.a=a; this.b=b;
}

하지만 빌더를 사용한다면 순서가 상관이 없게 된다.

Example.builder().a(a).b(b).build();

 

3) DB 계층 접근자 생성

Posts 클래스를 DB에 접근하게 해줄 PostsRepository.java를 생성한다.

반드시 Entity 클래스(Posts.java)와 함께 위치해야 하며, @Repository를 굳이 추가할 필요 없다.

public interface PostsRepository extends JpaRepository<Posts, Long> {}

Mybatis에서 Dao라고 불리는 DB Layer 접근자로, JPA에선 Repository라고 부르며 interface 형태로 생성한다.

JpaRepository<Entity클래스, PK타입>을 상속하면 기본적인 CRUD 메소드가 자동으로 생성된다.

 

4) 테스트 코드 작성하기

test 디렉토리에 domain/posts/PostsRepositoryTest.java를 생성한다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {
	@Autowired
	PostsRepository postsRepository;
	
	@After
	public void cleanup() {
		postsRepository.deleteAll();
	}
	
	@Test
	public void 게시글저장_불러오기() {
		//given
		String title	= "테스트 게시글";
		String content	= "테스트 본문";
		
		postsRepository.save(Posts.builder()
					.title(title)
					.content(content)
					.author("jojoldu@gmail.com")
					.build());
		
		// when
		List<Posts> postsList = postsRepository.findAll();
		
		// then
		Posts posts = postsList.get(0);
		assertThat(posts.getTitle()).isEqualTo(title);
		assertThat(posts.getContent()).isEqualTo(content);
	}
}

@After

- 단위 테스트 끝날 때마다 수행되는 메소드

- 배포 전 전체 테스트 수행할 때 테스트간 데이터 침범을 막기 위해 사용

- 여러 테스트 동시 수행 시 테스트용 db에 데이터가 남아있어 다음 테스트에 영향 있을 수 있음

 

@postsRepostiroy.save

- 테이블 posts에 insert/update 쿼리 실행

- id 값이 있다면 update, 없다면  insert

 

@postsRepository.findAll

- posts 테이블의 모든 데이터 조회

jUnit 상으로는 테스트가 완료 되었다!

실제로 쿼리가 실행된 형태가 궁금해지므로 src/main/resources/application.properties 파일을 생성한다.

spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true

다시 실행하면 sql문을 확인할 수 있다.


해당 게시글은 [ 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 / 이동욱 ] 책을 따라한 것을 정리하기 위한 게시글입니다. 요약, 생략한 부분이 많으니 보다 자세한 설명은 책 구매를 권장합니다.

 

반응형