이번 포스팅은 김영한 이사님의 스프링 부트 JPA 활용 1편을 듣고 제작되었습니다.
초기 세팅은 이렇게 진행하였고 강의가 나온 시점과 달리 스프링 부트가 3.XX로 업데이트도 됐고 이에 따라 자바도 17버전 이상을 사용해야하는 것을 유의해야 한다.
HHH90000021: Encountered deprecated setting [javax.persistence.sharedCache.mode], use [jakarta.persistence.sharedCache.mode] instead
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
또한 위와 같은 warning이 뜨는 것을 없애기 위해 yml파일을 수정하였다. 참고로 세팅은 properties가 아닌 yml형식을 사용하기로 하였다.

Lombok플러그인을 미리 깐 다음과 같이 Enable annotation processing을 체크해주어야 한다.
html파일을 변경할 때 마다 모든 파일을 다시 컴파일 하면 시간이 오래 걸린다. 이때 devtools를 사용하면 변경이 발생한 html파일만 리컴파일이 가능하다.
implementation 'org.springframework.boot:spring-boot-devtools'
dependencies에 다음과 같이 추가하고 서버가 실행된 상태에서
이렇게 html파일만 리컴파일 할 수 있다.
H2는 파일모드로 최소 한번 실행 후 네트워크모드로 설정해서 사용해야 한다.
먼저 JDBC URL을 jdbc:h2:~/jpashop로 한번 진행한 다음에 연결을 끊고 다시 jdbc:h2:tcp://localhost/~/jpashop이 주소로 실행하면 된다.
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/jpashop
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
#show_sql: true
format_sql: true
jakarta:
persistence:
sharedCache:
mode: ALL
open-in-view: false
logging:
level:
org.hibernate.SQL: debug
앞서 warning을 지웠던 세팅에 이어서 db커넥션과 jpa에 대한 세팅을 해준다. 참고로 show_sql=true는 sout을 sql을 찍게 되는데 이보단 org.hibernate.SQL옵션을 통해 logger에 실행 SQL을 남기는 것이 좋다고 한다.
이제 간단한 예제를 통해서 테스트를 진행해보자.
@Entity
@Getter @Setter
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
}
@Repository
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public Long save(Member member){
em.persist(member);
return member.getId();
}
public Member find(Long id){
return em.find(Member.class, id);
}
}
참고로 @PersistenceContext 어노테이션을 사용하면 별도의 주입 과정 없이 바로 스프링 빈으로 등록된 EntityManager를 스프링 컨테이너에서 가져올 수 있다고 한다.
@SpringBootTest
@Transactional
@Commit
class MemberRepositoryTest {
@Autowired MemberRepository memberRepository;
@Test
void save() {
//given
Member member = new Member();
member.setUsername("MemberA");
//when
Long saveId = memberRepository.save(member);
Member findMember = memberRepository.find(saveId);
//then
Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
Assertions.assertThat(findMember).isEqualTo(member);
}
@Test
void find() {
}
}
강의에선 Junit4를 사용하지만 아무래도 요즘은 Junit5로 많이 넘어간 추세인 것 같아 Junit5로 테스트를 생성하였다.
참고로 then 부분의 3번째 줄을 보면 우리가 생성한 멤버와 db에서 찾은 맴버가 동일하다는 것을 알 수 있는데 이것은 영속성 컨텍스트때문이라고 한다. 영속성 컨텍스트의 경우 JPA의 가장 핵심요소 중 하나라고 알고 있기 때문에 곧 따로 정리를 할 예정이다.
P6spy를 통해 JPA가 생성한 쿼리의 파라미터를 쉽게 볼 수 도 있고 포매팅도 할 수 있어 편하다. 하지만 스프링부트 3.XX부터 제작자가 공식적으로 지원을 하지 않아서 약간의 세팅을 우리가 해주어야 한다.
build.gradle의 dependencies에 다음을 추가해주자.
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.1'

resources에서 META-INF디렉토리와 하위에 spring디렉토리를 생성하고 다음과 같은 이름으로 파일을 생성하고 내용을 집어넣자.
파일명 :
org.springframework.boot.autoconfigure.AutoConfiguration.imports
내용 :com.github.gavlyukovskiy.boot.jdbc.decorator.DataSourceDecoratorAutoConfiguration
그 다음 마찬가지로 resources에서 다른 파일을 추가적으로 생성하자.
파일명 :
spy.properties
내용 :appender=com.p6spy.engine.spy.appender.Slf4JLogger
이렇게만 해도 동작을 하지만 줄바꿈을 적용해서 가독성을 좋게 하려면 프로젝트 패키지 하위에 다음 클래스를 생성하자.
package jpabook.jpashop;
import com.p6spy.engine.logging.Category;
import com.p6spy.engine.spy.P6SpyOptions;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import jakarta.annotation.PostConstruct;
import org.hibernate.engine.jdbc.internal.FormatStyle;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;
import java.util.Locale;
@Configuration
public class P6SpySqlFormatter implements MessageFormattingStrategy {
@PostConstruct
public void setLogMessageFormat() {
P6SpyOptions.getActiveInstance().setLogMessageFormat(this.getClass().getName());
}
@Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
sql = formatSql(category, sql);
return String.format("[%s] | %d ms | %s", category, elapsed, formatSql(category, sql));
}
private String formatSql(String category, String sql) {
if (sql != null && !sql.trim().isEmpty() && Category.STATEMENT.getName().equals(category)) {
String trimmedSQL = sql.trim().toLowerCase(Locale.ROOT);
if (trimmedSQL.startsWith("create") || trimmedSQL.startsWith("alter") || trimmedSQL.startsWith("comment")) {
sql = FormatStyle.DDL.getFormatter().format(sql);
} else {
sql = FormatStyle.BASIC.getFormatter().format(sql);
}
return sql;
}
return sql;
}
}
보기 좋게 쿼리와 파라미터가 포맷팅 된것을 확인할 수 있다.
자세한 정보는 이 글을 참고하자.
이렇게 본격적인 JPA 활용 실습 전에 모든 세팅을 완료하였다. 이미 스프링 입문 강의에서 다뤘던 내용들도 많고 세팅적인 부분도 이미 한번 다뤄봤기 때문에 빠르게 넘어갈 수 있었다.
기본편을 안듣고 활용편부터 듣다보니 개념적으로 부족할 수 는 있으나 우선 스프링에 대해서 아주 조금 찍먹을 해본 이 시점에서 바로 활용을 해보는 것도 괜찮을 것 같아 바로 이번 강의를 듣기로 결정하였다.
영속성 컨텍스트를 비롯한 핵심 개념들은 추후에 기본편을 들으면서 자세히 정리할 생각이다.
도움이 됐습니다. 감사합니다!