스프링 부트와 JPA 활용 1 - 웹 애플리케이션 개발 수업을 듣고 정리한 내용입니다.
JUnit4
를 기준으로 하기 때문에 build.gradle
에 있는 다음 부분을 꼭 직접 추가해야한다. 해당 부분을 입력하지 않으면 JUnit5
로 동작한다.//JUnit4 추가
testImplementation("org.junit.vintage:junit-vintage-engine") {
exclude group: "org.hamcrest", module: "hamcrest-core"
}
Enable annotation processing
체크Intellij IDEA
로 변경UTF-8
로 변경
✔️ 테스트 라이브러리
spring-boot-starter-test
junit
: 테스트 프레임워크mockito
: 목 라이브러리assertj
: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리 spring-test
: 스프링 통합 테스트 지원
핵심 라이브러리
기타 라이브러리
HikariCP
thymeleaf
)로깅 SLF4J
& LogBack
💡 참고
- 스프링 데이터 JPA는 스프링과 JPA를 먼저 이해하고 사용해야 하는 응용기술이다.
🔔 thymeleaf 템플릿 엔진
- thymeleaf 공식 사이트: https://www.thymeleaf.org/
- 스프링 공식 튜토리얼: https://spring.io/guides/gs/serving-web-content/
- 스프링부트 메뉴얼: https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-template-engines
- 스프링 부트 thymeleaf viewName 매핑 :
resources:templates/
+{ViewName}+.html
HelloController
package jpabook.jpashop;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HelloController {
@GetMapping("hello")
public String hello(Model model) {
// model에 데이터를 담아서 view에 넘길 수 있다.
model.addAttribute("data", "hello!");
// key : data, value : hello!
return "hello";
// return : 화면 이름 (resources -> templates -> hello.html
}
}
model
에 데이터를 담아서 view
에 넘길 수 있다.key
: data, value
: hello!return
: 화면 이름 (resources -> templates -> hello.html
), 스프링 부트가 반환 값 뒤에 .html
을 붙여준다.
hello.html
thymeleaf
템플릿엔진 동작 확인(hello.html
)<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Hello</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <p th:text="'안녕. ' + ${data}" >안녕하세요. 손님</p> </body> </html>
<p th:text="'안녕. ' + ${data}" >안녕하세요. 손님</p>
안녕하세요. 손님
이 출력된다.th:text="'안녕. ' + ${data}"
결과 값이 >여기(안녕하세요. 손님)<
으로 들어간다.➡️ 이것이 thymeleaf
의 장점이다!
순수 html
일 경우, static
디렉터리에 준다.
index.html
: 웹 사이트 접속할 때 실행되는 첫 화면이다. (localhost:8080
== localhost:8080/index.html
)
static/index.html
<!DOCTYPE HTML>
<html>
<head>
<title>Hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
Hello
<a href="/hello">hello</a>
</body>
</html>
/hello
경로로 이동
실행 결과
💡 참고
html
파일에서 화면 수정했을 때, 실행 상태에서 업데이트가 적용되도록 하기 위해spring-boot-devtools
라이브러리를 추가한다.
View file
변경 후,
Recompile hello.html
클릭한다.
이제 웹 사이트에서f5
를 눌릴 시 변경된 내용이 출력된다.➡️ 시행시, 서버 재시작 없이 브라우저에서 새로고침만 하면
View
파일 변경이 가능하다.
- 개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공
- 윈도우, 맥, 리눅스 실행 버전 다운로드 :
https://h2database.com/h2-2019-10-14.zip
- 공식 사이트 :
https://www.h2database.com
✔️ h2 설치전
SELECT H2VERSION() FROM DUAL
: h2 version 확인
✔️ 다운로드 및 설치
먼저 https://h2database.com/h2-2019-10-14.zip
설치한다.
사진에서 h2
디렉터리가 해당된다.
터미널에서
h2
가 있는 곳으로 이동한다. (이동할 때 띄워쓰기가 있다면 " "
으로 경로지정)h2Database
실행 방법 : ./h2.sh
h2Database
종료 방법 : ^
+ c
h2에서
첫 실행시
이렇게 뜨는데, 이제 경로 지정해줘야함
➡️
경로가 뜨는데, JDBC URL
경로가 무엇이냐면 이제 JDBC 파일 저장되는 경로이다.
연결버튼을 눌렸을 때 실행화면
h2DB를 종료하려면 그림으로 체크되어 있는 부분을 클릭하면 된다.
이제 터미널에서 ./h2.sh
로 실행한 후, 시작 경로를 입력하면(tcp
네트워크 모드로 접근) h2DB
가 실행된다.
나의 경로 : jdbc:h2:tcp://localhost//Users/leekyoungchang/Desktop/Study/Computer/Spring/JPA(pdf, ppt)/스프링 부트와 JPA 활용 1/자료/db/jpashop
main/resources/application.yml
spring: #띄어쓰기 없음
datasource: #띄어쓰기 2칸
url: jdbc:h2:tcp://localhost//Users/leekyoungchang/Desktop/Study/Computer/Spring/JPA(pdf, ppt)/스프링 부트와 JPA 활용 1/자료/db/jpashop # 여기해결해야함 14:01분
username: sa
password:
driver-class-name: org.h2.Driver
jpa: #띄어쓰기 2칸
hibernate: #띄어쓰기 4칸
ddl-auto: create #띄어쓰기 6칸
properties:
hibernate:
# show_sql: true
format_sql: true #띄어쓰기 8칸
logging:
level:
org.hibernate.SQL: debug
# org.hibernate,type: trace
spring.jpa.hibernate.ddl-auto: create
: 이 옵션은 애플리케이션 실행 시점에 테이블을 drop
하고, 다시 생성한다.
💡 참고
- 모든 로그 출력은 가급적 로거를 통해 남겨야 한다.
show_sql
: 옵션은System.out
에 하이버네이트 실행 SQL을 남긴다.org.hibernate.SQL
: 옵션은logger
를 통해 하이버네이트 실행SQL
을 남긴다.
⚠️ 주의
application.yml
같은yml
파일은 띄어쓰기(스페이스) 2칸으로 계층을 만든다. 따라서 띄어쓰기 2칸을 필수로 적어주어야한다.
Member
package jpabook.jpashop;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter @Setter
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
}
MemberRepository - 회원 리포지토리
package jpabook.jpashop;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@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 : 자동으로 엔티티 매니저를 주입해준다.implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
사용 시, 엔티티 매니저가 자동으로 생성되고 application.yml
에 있는 jpa:
설정을 읽고 엔티티 매니저 팩토리 관련 모든 것들이 자동으로 생성되고 실행된다.
MemberRepositoryTest - 테스트
package jpabook.jpashop;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import javax.transaction.Transactional;
import static org.assertj.core.api.Assertions.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Test
@Transactional
@Rollback(false)
public void testMember() throws Exception{
// given
Member member = new Member();
member.setName("memberA");
// when
// 엔티티를 수정하기 위해서는 트랜잭션안에서 실행되어야 한다.
Long saveId = memberRepository.save(member);
Member findMember = memberRepository.find(saveId);
// then
assertThat(findMember.getId()).isEqualTo(member.getId());
assertThat(findMember.getName()).isEqualTo(member.getName());
assertThat(findMember).isEqualTo(member);
System.out.println("findMember == member: " + (findMember = member));
}
}
@Transactional
: 엔티티는 트랜잭션안에서 실행되기 때문에, 엔티티 데이터들을 수정할 때는 @Transactional
애노테이션을 사용해야 한다.@Rollback(false)
: Test 끝난 후, Rollback을 하게 된다. (원래는 auto로 해주지만, 그렇지 않을 경우가 있기 때문에 직접 삽입하는 것이 좋고, Rollback을 하지 않고 커밋을 할려면 이와 같이 삽입하면 된다.)
⚠️ 주의
@Test
는JUnit4
를 사용하면org.junit.Test
를 사용해야한다.
Entity
, Repository
동작 확인jar
빌드해서 동작 확인
❌ 오류
테스트를 실행했는데 다음과 같이 테스트를 찾을 수 없는 오류가 발생하는 경우
No tests found for given includes: [jpabook.jpashop.MemberRepositoryTest] (filter.includeTestsMatching)
해결 : 스프링 부트 2.1.x 버전을 사용하지 않고, 2.2.x 이상 버전을 사용하면 Junit5
가 설치된다. 이때는 build.gradle
마지막에 다음 내용을 추가하면 테스트를 인식할 수 있다. Junit5
부터는 build.gradle
에 다음 내용을 추가해야 테스트가 인식된다.
build.gradle
마지막에 추가
tasks.named('test') {
useJUnitPlatform()
}
💡 참고
- 스프링 부트를 통해 복잡한 설정이 다 자동화 되었다.
persistence.xml
도 없고,LocalContainerEntityManagerFactoryBean
도 없다.- 스프링 부트를 통한 추가 설정은 스프링 부트 메뉴얼을 참고하고, 스프링 부트를 사용하지 않고 순수 스프링과 JPA 설정 방법은 자바 ORM 표준 JPA 프로그래밍 책을 참고하자!
✔️ MemberRepositoryTest 실행했을 때
drop : 테이블 날림
create
Entity
➡️ 위 실행시, Entity
정보를 보고 생성한다.
Test
에서 데이터 입력 db에 저장되었는지 체크할 때
@Rollback(false)
사용한다.
memberA
추가memberA
출력됨
✔️build를 clean 하려고했는데 안됨(가상서버를 실행시켜야 함)
./gradlew clean build
로 build
를 clean
하려고 했다.
하지만, 이와 같은 오류가 발생한다.
어 그런데
test
도 갑자기 실행이 안된다.
BUILD FAILED
이 왜 발생하는 걸까?
build
정리 및 test
를 실행하려고 했기 때문에, 발생한 오류이다.application.yml
datasource
에서 url
을 실행했는데 가상서버가 close되어 있으니, 실행에서 오류가 발생한 것 같다. (개인적 생각)
그러므로 ./h2.sh
실행하여 가상서버를 켜주면, 위 발생했던 오류들이 해결된다.
터미널에서 Intellij대신 실행
위와 같이 가상 서버 open한 상태에서 실행해야 한다.
이전부터 이런 형식으로 출력되면 답답했었다.
✔️ 꿀팁
(1) 로그에 다음을 추가하기 org.hibernate.type
: SQL 실행 파라미터를 로그로 남긴다.
(2) 외부 라이브러리 사용 : https://github.com/gavlyukovskiy/spring-boot-data-source-decorator
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.6'
커넥션을 얻어오는 것도 모니터링이 가능하다. 현재 테이블에 입력된 내용들이 무엇인가도 알 수 있다.
추가적 설정들은 https://github.com/gavlyukovskiy/spring-boot-data-source-decorator
여기를 참고하여 추가하면 된다.
💡 참고
build.gradle
에서
springboot 2.1.7
부터는 나에게 잘맞는 버전을 궁합하여 맞추어준다.- 그러므로
2.1.7
이후 버전부터는 뒤에 버전을 적지 않아도 된다.- 그러나, 이전 버전까지는 직접 버전을 입력해줘야 한다.
💡 참고
- 쿼리 파라미터를 로그로 남기는 외부 라이브러리는 시스템 자원을 사용하므로, 개발 단계에서는 편하게 사용해도 된다.
- 하지만 운영시스템에 적용하려면 꼭 성능테스트를 하고 사용하는 것이 좋다.
- 지금은 개발 단계이므로 마음 껏 사용해도 된다.
- 나중에 운영에서는 성능테스트가 중요해서
쿼리 파라미터 로그 남기기
설정들이 성능테스트에 문제가 없으면 사용하면 된다.