본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
Spring boot

스프링 부트 - JPA 와 DB 설정 및 동작확인

by 방구석 대학생 2020. 12. 20.

"인프런 - 실전! 스프링 부트와 JPA 활용1 강의를 듣고 작성한 글 입니다."

www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1#

 

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런

실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다. 초급

www.inflearn.com

 

프로젝트에서 JPA 와 DB 간 연동을 해보자.

우선 아래와 같이 프로젝트 환경 설정 파일 application.yml 를 작성해보자.

(프로젝트 환경 설정의 경우 스프링 공식문서를 살펴보면 자세히 알 수 있다.)

- application.yml

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/jpashop_practice;
    username: sa
    password:
    driver-class-name: org.h2.Driver
    
  jpa:
    hibernate:
      ddl-auto: create
    properties: 
      hibernate:
       # show_sql: true
        format_sql: true

logging:
  level:
    org.hibernate.SQL: debug  

 

위의 파일에서 중요하게 살펴봐야 할 건 logging 옵션이다.

logging 옵션을 살펴보면 org.hibernate.SQL : debug 가 지정되어 있는데, 이는 Hibernate SQL 로그를 디버그 모드로 사용 하게끔 해주는 옵션이다.

이렇게 하면 Hibernate 가 남기는 모든 로그들이 디버그 모드가 되어 JPA 나 Hibernate 가 생성하는 모든 SQL 이 다 보이게 된다.(위에서 # 으로 주석처리된 show_sql 옵션보다 되도록이면 이걸 쓰도록하자.)

 

이렇게 프로젝트의 설정을 마쳤으면 이젠 스프링 부트가 정상적으로 잘 동작하는지 실험해보자.

Entity 클래스를 하나 생성한다.

- Member.java

@Entity
@Getter
@Setter
public class Member {

	@Id @GeneratedValue
	private Long id;
	private String username;

}

 

이후 Repository 클래스를 하나 만든다.

- MemberRepository.java

@Repository
public class MemberRepository {

	@PersistenceContext // 영속성 컨텍스트 어노테이션(JPA를 사용하기 때문에 EntityManager 가 필요하다.)
	private EntityManager em;
	// 스프링 부트에서 위와 같은 어노테이션이 있으면, EntityManager 를 주입해준다.

	public Long save(Member member){ // Entity 객체를 저장하기 위한 메소드
		em.persist(member);
		return member.getId(); // 왜 키 값을 반환해야 할까?
		// 커맨드와 쿼리를 분리하라?(CQRS?) -> 당장은 알 필요 없는것 같다.
		// 저장을 하고 나면 사이드 이펙트를 일으킬 수 있는 커맨드 성 이기 때문에
		// 리턴 값을 거의 만들지 않는다.
		// 대신 아이디 값을 받아올 수 있으면 다음에 다시 조회 정도는 할 수 있다.
	}

	public Member find(Long id){
		return em.find(Member.class, id);
	}
}

 

이번엔 테스트 코드를 작성해보자.

Repository 클래스에서 Shift + Ctrl + T 를 누르면 자동으로 테스트 코드 클래스를 생성해주는 기능을 실행시킬 수 있다.

- MemberRepositoryTest.java

@RunWith(SpringRunner.class) //Junit 에게 스프링으로 테스트 한다고 알려주기 위한 어노테이션
@SpringBootTest
public class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;
    
    @Test
    public void testMember() throws Exception {
    
    	Member member = new Member();
        member.setUsername("memberA");

        // Ctrl + Alt + V 단축키를 활용하여 코드에서 원하는 변수를 뽑아낼 수 있다.
        Long saveId = memberRepository.save(member);
        Member findMember = memberRepository.find(saveId);
        
        // 검증에서 Assertions 는 assertj 라는 라이브러리를 스프링 테스트가 자동으로 가지고 있다.
        Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
        Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
   }     
}

 

그런데 위와 같이 코드를 작성한 후 테스트를 수행하면 아래와 같은 에러가 발생하게 된다.

No EntityManager with actual transaction available for current thread

위의 에러는 바로 트랜잭션이 존재하지 않는다는 뜻이다.

 

EntityManager 를 통한 모든 데이터 변경은 항상 트랜잭션 내부에서 수행되어야 한다.

그렇기 때문에 메소드에서 @Transactional 어노테이션을 선언하여 트랜잭션을 만들어 준 후 메소드를 실행해야 한다.

 

- MemberRepositoryTest.java

@Test
@Transactional
public void testMember() throws Exception {
      ..........
}

 

위와 같이 어노테이션을 통해 트랜잭션을 생성해준 후 테스트가 성공했을 때 H2 콘솔을 통해 데이터베이스의 상태를 보면 Member 테이블이 생성되어 있는 것을 확인할 수 있다.

 

하지만 테스트 코드에서 데이터를 save 메소드를 통해 삽입하였음에도 불구하고 데이터베이스에 데이터가 들어가 있지 않다.

어째서 그런것일까>

 

그 이유는 바로 일반적으로 테스트가 아닌곳에 Transactional 어노테이션이 있으면  정상적으로 트랜잭션이 수행되서 데이터가 저장되나, Test 에 있는 경우 데이터베이스를 롤백 해버림으로서 데이터가 모두 날아가지 때문이다.

여기서 데이터베이스를 롤백하는 이유는, 데이터가 그대로 남아있으면 반복적인 테스트를 하기 힘들기 때문이다.

그런데 여기서 @Rollback(false) 어노테이션을 하나 더 선언해주면 데이터베이스가 롤백되지 않고 데이터를 그대로 남겨두고 있는 것을 확인할 수 있다.

- MemberRepositoryTest.java

@Test
@Transactional
@Rollback(false) // 데이터베이스 롤백 방지
public void testMember() throws Exception {
      ..........
}

 

그렇다면 이번엔 아래의 코드를 실행해보자.

- MemberRepositoryTest.java

@Test
@Transactional
public void testMember() throws Exception {
      ..........
      
      Assertions.assertThat(findMember).isEqualTo(member);
      System.out.println("findmember == member : " + (findMember == member));
}

위의 코드의 결과는 어떻게 나올까? (findMember 와 member 의 == 비교)

비교 결과 true 가 출력된다.

 

어째서일까?

이전 JPA 강의를 들을때 배웠던 내용에서 처럼 한 트랜잭션에서 같은 영속성 컨텍스트에 있을 경우 아이디 값이 같다면 같은 Entity 로 보기 때문이다.

생성된 Hibernate SQL 을 잘 보면 select 절 조차 나오지 않은 것을 잘 알 수 있다.

- Hibernate SQL

findmember == member : true