당니의 개발자 스토리

JPA와 DB 설정, 동작확인 본문

스프링/실전! 스프링 부트와 JPA 활용1

JPA와 DB 설정, 동작확인

clainy 2024. 4. 13. 12:13

JPA와 DB 설정, 동작확인

이번 시간에는 JPA데이터베이스를 설정하고 실제 동작하는지 확인해 보겠습니다.

먼저 IntelliJ로 돌아가서 application.properties를 지우고, 저는 YAML을 좋아해서 application.yml 파일을 만들겠습니다.

.properties를 쓰던가 yaml을 쓰던가 둘 중에 하나를 선택하시면 되는데 설정파일이 많아지고 복잡해지면 저는 yaml이 더 낫더라구요.

 

자 그래서 이제 먼저 세팅을 해야 되겠죠. datasource 부터 설정을 할 건데, datasource란 DB와 관계된 커넥션 정보를 갖고 있으며, 빈으로 등록하여 인자로 넘겨줍니다.

이 과정을 통해 Spring은 datasource로 DB와의 연결을 획득합니다. 그래서 datasource Spring이랑 DB 서버를 연결해주는 거죠.

일단 jdbc:h2:tcp://localhost/~/jpashop에 접근하면 되니까 이렇게 적어줍니다.

그 다음에 driver-class-name 이라는 특성이 있는데요. 얘는 우리가 h2를 쓰기 때문에 org.h2.Driver 하면 데이터베이스 커넥션과 관련된 datasource 설정이 완료가 됩니다.

이렇게만 딱 해도 HikariCP를 써서 커넥션 풀이나 이런 걸 다 스프링 부트가 세팅을 걸어줍니다.

그 다음에 jpa와 관련된 세팅을 할 건데요. jpa는 우선 hibernate, 이 setting에 가서 ddl-autocreate 모드로 할 겁니다. create는 자동으로 테이블을 만들어 주는 모드죠.

그리고 properties는 hibernate와 관련된 특정한 property들을 쓸 수가 있는데요. 이러한 설정들을 걸어주면 됩니다.

이러한 설정들은 스프링 부트 매뉴얼에 들어가셔서 공부하셔야 해요.

spring.io에 들어가셔서, Projects에서

여기 Learn에서 Reference Doc에 들어가시면,

엄청 많습니다. 하나하나 다 배워야 됩니다.

이렇게 검색해서 옵션이 뭔지 찾으시면 됩니다.

그 다음에 로그 레벨을 정할 겁니다.

Hibernate SQL 로그를 디버그 모드로 쓴다는 건데, 이렇게 하면 Hibernate가 남기는 모든 로그가 다 디버그 모드로 해서, JPA나 Hibernate가 생성하는 SQL이 다 보입니다.

'그런데 선생님 그러면 이 두 개가 똑같은 거 아닌가요?'

이 두 개의 차이가 뭐냐면 show_sqlSystem.out에 출력을 하는 거고요. org.hibernate.SQL은 로거를 통해서 찍습니다.

그래서 show_sql은 안 써야 되겠죠. 주석처리를 해줍니다.

운영 환경에서는 모든 로그들은 시스템 아웃으로 찍으시면 안되고 다 로거를 통해서 찍어야 됩니다.

자 그리고 이 옵션은

애플리케이션 실행 시점에 내가 가지고 있는 테이블을 다 지우고, 내가 가지고 있는 엔티티 정보를 보고 다시 생성하는 겁니다.


자 그럼 여기까지만 하면 일단 기본적인 설정 파일은 설정이 끝났습니다.

그 다음에 이제 실제 동작하는지 확인을 해볼게요.

 

먼저 회원 엔티티를 하나 만들어 보겠습니다.

여러분 이제부터는 JPA를 안다고 가정을 하고 들어갑니다.

Entity를 만들어 놓습니다. Entity는 JPA 에서 테이블에 대응하는 하나의 클래스라고 생각하면 편해요.

그리고 나서,

식별자를 @Id 맵핑을 해주고, 그 다음에 @GeneratedValue로 해서 자동 생성되게 만들겠습니다. 그럼 데이터베이스가 자동 생성을 해주겠죠.

그리고 id랑 username 이라는 속성 정도만 가지고 있고, 그 다음에 cmd + N 해서 getter, setter로

이렇게 해주셔도 되는데, 우리는 Lombok을 쓰니까

이렇게만 해줘도 getter, setter가 자동 생성 됩니다.

 

그 다음에 이제 리포지토리를 만들어 보겠습니다.

MemberRepository가 뭐냐면 엔티티 같은 걸 찾아주는 겁니다.

이 어노테이션은 Spring이 제공하는 기본 타입이죠. 그러니까 컴포넌트 스캔의 대상이 되는 어노테이션 중에 하나죠. @Repository를 들어가면,

여기 @Component가 되어있죠. 그래서 컴포넌트 스캔의 대상이 돼서 자동으로 스프링 빈에 등록이 되겠죠.

 

JPA를 쓰기 때문에 이제 엔티티 매니저가 있어야 되잖아요.

엔티티 매니저를 @PersistenceContext 라고 해주시고 엔티티 매니저를 만듭니다.


@PersistenceContext 란?

  1. EntityManager를 빈으로 주입할 때 사용하는 어노테이션입니다.
  • 스프링에서는 영속성 관리를 위해 EntityManager가 존재합니다.
  • 그래서 스프링 컨테이너가 시작될 때 EntityManager를 만들어서 빈으로 등록해둡니다.
  • 이 때 스프링이 만들어둔 EntityManager를 주입받을 때 사용합니다.

 

  2. @PersistenceContext로 지정된 프로퍼티에 아래 두 가지 중 한 가지로 EntityManager를 주입해줍니다.

  • EntityManagerFactory에서 새로운 EntityManager를 생성하거나
  • Transaction에 의해 기존에 생성된 EntityManager를 반환해줍니다.

이렇게 하면 저희가 스프링 부트를 쓰기 때문에 스프링 컨테이너 위에서 다 모든게 동작한단 말이예요. 그래서 스프링 부트가 이 @PersistenceContext 어노테이션이 있으면 엔티티 매니저를 주입해줍니다. 그래서 그냥 쓰시면 됩니다.

내가 설정을 따로 안해줬죠. 지금 엔티티 매니저 막 팩토리를 생성하는 코드가 없는데도 스프링 부트가 이거를 다 해줍니다.

여러분 이거 등록 하셨던거 기억나시죠? 여기 spring-boot-starter-data-jpa 를 등록하면서 그냥 엔티티 매니저를 생성하는게 자동으로 들어가고요.

여기는 이 설정 파일을 다 읽어가지고 엔티티 매니저를 생성하는 코드, 팩토리 같은 코드가 다 생성이 돼서 만들어집니다. 그래서 우리는 그냥 쓰면 됩니다.

그 다음에 저장하는 코드 한번 만들어 볼까요.

이렇게 만들었구요. 근데 여러분 이제 약간 '어 왜 이렇게 하지? member를 반환하면 되잖아. 귀찮게 왜 아이디만 반환하지?'

이거는 김영한t의 스타일인데요, '커맨드랑 쿼리를 분리해라' 그 원칙에 의해서 저장을 하고나면 가급적이면 이거는 사이드 이펙트를 일으키는 커맨드성이기 때문에 리턴값을 거의 안 만듭니다. 대신에 아이디 정도 있으면 다음에 다시 조회할 수 있으니까 이렇게 아이디 정도만 조회하는 걸로 저는 주로 설계를 합니다.

 

그 다음에 멤버를 하나만 조회하는 메서드를 만듭니다.

여러분 이제 jpa 다 아시니까 요정도는 하실 수 있죠.

자 그럼 이제 되는지 테스트 해보겠습니다.

테스트 코드를 만들려면 command + shift + T 단축키로 create a new test를 선택하고,

저는 그냥 Junit4로 하겠습니다. 요즘은 이제 Junit5를 많이 쓰지만 그냥 Junit4로 할게요.

그러면 테스트 케이스가 이렇게 생성됐죠. 근데 이제 스프링 부트로 테스트를 돌려야 되기 때문에

@SpringBootTest를 적어줍니다.

그런데 이것만 있으면 안되죠. JUnit 한테 알려줘야 되죠.

'나 스프링과 관련된 걸로 테스트를 할 거야' 라고 알려줘야 돼서 @RunWith 이랑 @SpringBootTest 어노테이션 2개를 넣어줍니다.

JUnit5 부터는 @RunWith 을 안 써도 된다고 합니다. 

그리고 MemberRepository가 잘 되는지 볼 거니까 @Autowired로 해서 MemberRepositoryinjection을 받고, 테스트를 만듭시다.

@Test를 적고 나서

이렇게 만듭니다. 저는 given, when, then 스타일을 되게 좋아하는데요.

given은 뭐냐면, 이러한 환경, 즉 이러한 데이터가 주어졌을 때를 말하고, when 이걸 검증한다 라는 거고, then검증한 결과어떻게 나오는지 확인하는 검증부입니다.

참고로 김영한t는 tdd + tab 해서 빠르게 빠르게 만들었는데

여기서 custom으로 만들 수 있습니다.

반환값과 변수명은 option + cmd + V 하면 저장된 아이디를 뽑고 내가 세이브한게 잘 저장됐는지 이제 검증해 보면 되겠죠.

when과 then이 애매한 부분이 있긴 한데 then 절에는 검증부만 넣겠습니다.

검증은 Assertions라고 해서 assertj 건데, assertj 라는 라이브러리를 스프링 테스트가 자동으로 가지고 있기 때문에 라이브러리 Dependency에서 얘가 그냥 올라와요. assertj의 장점이 바로바로 '.'을 찍어서 할 수 있다는 게 장점이거든요.

assertThat 해가지고 findMember의 id가 is equal to 해가지고 member의 id랑 같은지, 추가로 cmd + D 해서 복사하고,

getUserName이 여기 getUserName이 같다. 검증하면 되겠죠.

그런데 이거를 실행을 해보면 에러가 나야 합니다. 왜 에러가 났을까요? 한번 고민해 보셔야 됩니다.

왜냐면 no entity manager with actual transaction. 그러니까 트랜잭션이 없다는 거에요.

엔티티 매니저를 통한 모든 데이터 변경은 항상 트랜잭션 안에서 이루어져야 되거든요. JPA 기본편에 잘 설명이 되어 있습니다. 그래서 트랜잭션이 있어야 합니다. 그런데 지금 트랜잭션이 전혀 없죠.

변경이 일어나는 부분에다가 걸어줘야 하는데, 여기 MemberRepository에다가 트랜잭션을 걸어줘도 되지만 우선은 테스트 케이스에 트랜잭션을 걸겠습니다.

참고로 트랜잭션 어노테이션은 두 가지가 있거든요.

스프링 프레임워크에서 제공하는 것도 있고, jakarta 라는 자바 표준이 제공하는 트랜잭션도 있는데, 우리는 이미 스프링 프레임워크를 쓰기 때문에 스프링에 종속적으로 설계를 하므로,

@Transactional 어노테이션, 스프링 거를 쓰시는 걸 권장합니다. 왜냐하면 여기에서 쓸 수 있는 옵션이 많거든요.

 

자 이제 돌려보겠습니다. 그럼 이 테스트를 실행할 때 트랜잭션에 걸리면서 실행하거든요.

이렇게 녹색불이 들어오면 되는거죠. 주의할 점은 ./h2.sh를 꼭 실행시킨 상태에서 돌려야 한다는 겁니다.

 

자 그러면 DB 한번 가볼까요?

이 JDBC URL이 application.yml에 있는

url과 당연히 맞춰야겠죠.

들어와보면, 왼쪽에 Member 라는 테이블이 생겨나 있습니다. jpa가 create 옵션이 있으니까 테이블을 만들어 준 거죠.

왜냐하면 어플리케이션을 실행할 때, 이렇게 뜹니다.

이렇게 ddl-auto: create를 해놨기 때문이죠.

그래서 먼저, drop table if exists 해서 테이블을 날리고 그 다음 create를 하고 끝납니다.

자 아무튼 엔티티 정보를 보고 이제 테이블을 쭉 생성해내는 거죠.

여기 이 Member Entity 정보를 보고 테이블을 만듭니다. 그래서 테이블 생성된 걸 보시면,

맵핑했던 정보대로 딱딱 생성이 됐죠.

그런데 이상한 게 '왜 데이터가 없지?' 하실 거예요. 스프링을 잘 아시는 분들은 아실텐데 이 스프링 Transactional Annotation이 테스트 케이스에 있으면 이 테스트가 끝난 다음에 바로 롤백을 해버립니다. 일반적으로 테스트가 아닌 곳에 @Transactional이 있으면 정상적으로 잘 동작을 하는데, 테스트에 있으면 테스트가 끝난 다음에 db를 롤백 해버려요. 사실 그게 맞겠죠.

왜냐면 데이터가 들어가 있으면 반복적인 테스트를 못하잖아요. 그래서 다 롤백을 해버립니다.

그런데 이제 또 살다보면 이 Assertions만 가지고 내 코드를 또 의심한단 말이에요. 내 눈으로 보고 싶을 때가 있죠.

그럼 @Rollback(false) 해주시고 다시 돌려주시면,

insert 라고 뜨죠. 다시 h2 데이터베이스에서

실행을 누르면 이렇게 데이터가 들어와있는 걸 볼 수 있습니다. 이 @Rollback(false)을 넣어주시면 롤백을 안하고 그냥 커밋 해버리는 거에요.


커밋이라는 걸 해줘야 db에 반영이 된다.

기본적으로는 트랜잭션이라는 개념이 있어서, insert 쿼리를 한 다음에 이거를 커밋하기 전까지는 db에 반영이 안된다.

그런데 테스트 끝난 다음에 롤백을 해버리면 어떻게 될까?

db에 인서트 쿼리를 날리고 다 한 다음에, 커밋하기 전에 롤백을 해버리는 거다.

롤백은 데이터베이스에서 업데이트에 오류 등이 발생할 때, 이전 상태로 되돌리는 것을 말한다.


자 이렇게 해보면 이제 여러분들이 JPA가 잘 동작하고 있구나, 지금 세팅이 잘 됐구나라는 걸 확인하실 수가 있습니다.


소위 말하는 JPA라는 게 ORM 기술인데, ORM 기술은 DB에서 쿼리 없이 자바 객체를 편하게 저장하고 꺼내는 기술이다.


그럼 여기서 재밌는 테스트 하나만 더 해볼까요.

지금 뭐 따로 equals 해시코드 같은거 구현한 게 없기 때문에 그냥 '==' 비교라고 보시면 돼요.

자 여러분 잠깐 생각을 해보시고 돌려볼게요. findMember랑 member랑 같을까요, 다를까요?

자 기본편을 충실하게 공부하셨던 분들은 이게 true가 나온다는 걸 아시겠죠.

이거 제가 그냥 시스템 아웃으로 출력해 볼게요.

이렇게 하고 돌려보면,

이거는 true 가 되어야 돼요. 왜냐하면 지금 같은 트랜잭션 안에서 이거를 저장을 하고 조회하면 영속성 컨텍스트가 똑같겠죠. 같은 영속성 컨텍스트 안에서는 id 값이 같으면 같은 엔티티로 식별하는 거죠.

영속성 컨텍스트에서 식별자가 같으면 같은 엔티티로 인식한다고 보시면 되고, 1차 캐시라고 불리는 거기서 이미 영속성 컨텍스트에 똑같은 엔티티가 관리되고 있는 게 있기 때문에 그냥 기존에 관리하던 게 나와버린 거죠.

그래서 여기 보시면 아예 select 쿼리조차 안 나간 거예요. '어? 내 영속성 컨텍스트에 있네' 하고 그냥 1차 캐시에 있는 데서 그냥 쭉 꺼내와버린 거죠. 혹시 이 내용 이해가 잘 안 되시면 기본편을 한번 쭉 충실히 들으셔야 됩니다.


자 여기까지 하면 대략적으로 jpa 부분 세팅이 끝났네요.

교안을 보면 몇 가지 꿀팁들을 적어놓은게 있네요.

그럼 이제 여기에 나와있는 것들을 확인해보면서 이 샘플을 만든 게 제대로 동작할까 를 한번 볼게요.

gradlew clean build를 하면 깔끔하게 지우고 다시 빌드하는 거라서 방금 제가 만든 테스트들이 실행됩니다.

테스트가 잘 끝나고 나면 여기 jpashop/build/libs 파일에 jpashop-0.0.1-SNAPSHOT.jar이 생깁니다. 라이브러리가 요새 많아가지고 48메가네요.

그 다음에

java -jar jpashop-0.0.1-SNAPSHOT.jar 하고 엔터치면 쭉 스프링이 올라가겠죠.

자 이렇게 되면 배포같은거 할때 이 파일을 가져다가 던지면 되겠죠. 그리고 나서, localhost:8080 하면,

이렇게 뜨고, hello를 누르면

잘 나옵니다.

이제 나가고요.

자 그 다음에 스프링 부트를 통해서 이러한 복잡한 설정이 다 자동화되어 있어요. 이제 부트의 매뉴얼을 보시면,

Spring Boot 쪽이나 데이터 액세스 부분 쭉 찾아보시면 어떤 Configuration 이런 거에 대해서 잘 나와있습니다. 그래서 스프링 부트가 다 자동으로 해버리기 때문에 persistence.xml 파일을 여러분이 안 만지셔도 돼요. Spring이 persistence.xml 없이 할 수 있는 메커니즘을 다 제공하거든요.

그리고 참고로 예전에 부트 없이 순수하게 스프링이랑 JPA를 쓰셨던 분들은 LocalContainerEntityManagerFactoryBean 이라는 게 있어야 된다고 아셨는데 이것도 스프링 부트가 다 자동으로 등록을 해줍니다.

그래서 만약에 순수하게 JPA랑 스프링을 쓰시는 거에 대해서 궁금하신 분들은 제 책, 자바 ORM 표준 JPA 프로그래밍 책을 참고하시면 됩니다.

근데 웬만하면 여러분 boot로 boot 매뉴얼을 보고 거기서 필요한 것들을 찾아서 application.yml에다가 설정해서 쓰시는 것을 권장해드립니다.


자 그 다음에 이제 여러분 어마어마한 꿀팁 query parameter log 남기기 인데요.

이거를 알면 개발할 때 엄청 편합니다.

이게 JPA를 쓰면 답답한 게 SQL이 나가는 거랑 데이터베이스 커넥션 가져오고, 이게 도대체 어느 타이밍에 일어나지? 궁금할 때가 되게 많거든요.

쿼리 파라미터 찍는 게 약간 별로예요.

지금 실행한거 보면 쿼리 파라미터가 안남았죠? 물음표 물음표로 남는단 말이에요. 가끔 공부할때 보면 개발할때나 좀 답답하거든요. 그래서 org.hibernate.type: trace 라는 라이브러리가 있습니다. 스프링 부트 버전 3.x 부터는 org.hibernate.orm.jdbc.bind: trace 써야해요.

먼저 첫번째 방법 쿼리 파라미터를 찍는거. 로깅 할 때 org.hibernate 로거의 type을 trace로 주시고 다시 테스트 케이스를 돌리면,

이렇게 찍어 줍니다. 좋죠. 근데 여기서 보통 만족을 못하죠.

그래도 '이 물음표 부분이 뭔지 한 눈에 보고 싶은데' 한단 말이에요.

그래서 이 외부 라이브러리를 쓰시면 됩니다.

제가 써보고 중에는 이게 좀 제일 편했어요. 스프링 부트 데이터 소스 데코레이터 라는 게 있는데 이게 뭐냐면, 데이터베이스 커넥션을 원래 맵핑 해가지고 왔다 갔다 하는 만들어진 SQL statement를 이해해가지고 로고로 출력해주는 라이브러리도 좋은게 많아요.

그 중에서 이제 뭐 p6Spy 라던가 Datasource Proxy 뭐 이런 애들이 있는데 p6Spy를 이번에 한번 써보겠습니다.

퀵 스타트를 딱 보고 이거 라이브러리를 하나 추가해 주시면 됩니다.

버전이 1.9.0으로 해야겠죠.

build.gradle에 가서 추가해주면 됩니다.

'그런데 얘는 버전 정보가 있는데, 위의 것들은 버전 정보가 왜 없죠?' 하실 수도 있어요.

혹시 모르시는 분들을 위해서 기본적으로 스프링 부트가 '나한테 궁합이 맞는 라이브러리 버전이 뭐야?' 하고 웬만한 라이브러리들은 다 세팅을 해놨어요.

그런데 스프링 부트가 지원하지 않은, 미리 세팅을 안해놓은 라이브러리들은 이렇게 버전 정보를 직접 적어 주셔야 됩니다.

참고로 h2 드라이버나 롬복 같은 것도 스프링 부트가 본인한테 궁합이 맞는 라이브러리 정보를 다 세팅을 해놓은 거에요.

이렇게 해놓고 refresh 하면, 라이브러리를 다운로드 받으실 거에요.

그 다음에 세팅할 게 없습니다. 이게 여러분 스프링 부트의 어마어마한 장점이죠. auto configuration을 가지고 다 쫘악 세팅을 해줍니다.

자 이제 테스트를 바로 돌려 볼게요.

p6spy 해서 이렇게 나오죠. 첫번째는 원본이 출력되고 두번째는 파라미터 바인딩이 출력이 되죠.

그리고 커넥션을 얻어오는 거나 이런 것도 다 모니터링이 됩니다.

그리고 여기 있는 로고를 어떤 식으로 찍을 건지 등에 대한 옵션은

여기서 여러분이 찾아보시고 설정들이 있거든요.

이거 보시고 수정해서 쓰시면 됩니다. 여기 있는 application.properties를 이제 application.yml로 YAML 스타일로 바꿔서 적으면 다 설정하실 수가 있습니다.

근데 참고로 이런 라이브러리들은 개발 단계에서는 편한데 운영에서 배포할 때는 좀 고민을 해보셔야 돼요.

운영에서도 쓰면 좋겠지라고 생각하지만, 이게 성능 테스트를 꼭 해보셔야 돼요. 이런 것들이 잘못하면 성능을 확 저하시킬 수도 있거든요. 그래서 병목이 될 수 있기 때문에 성능 테스트를 해보시고 이 정도면 받아들일만 하다라고 하시면 그냥 운영에서 쓰셔도 되지만, 그렇지 않으면 개발 정도에서만 쓰시는 것을 권장합니다.

 

자 여러분 그래서 대단원의 프로젝트 환경 설정이 다 끝났네요.

다음은 도메인 분석 설계를 들어가도록 하겠습니다.

'스프링 > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글

도메인 모델과 테이블 설계  (0) 2024.04.15
요구사항 분석  (0) 2024.04.13
H2 데이터베이스 설치  (0) 2024.04.02
View 환경 설정  (0) 2024.03.31
라이브러리 살펴보기  (0) 2024.03.31