관리 메뉴

나구리의 개발공부기록

프로젝트 환경설정, 프로젝트 생성, 라이브러리 살펴보기, View 환경 설정, H2 데이터베이스 설치, JPA와 DB설정, 동작확인 본문

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

프로젝트 환경설정, 프로젝트 생성, 라이브러리 살펴보기, View 환경 설정, H2 데이터베이스 설치, JPA와 DB설정, 동작확인

소소한나구리 2024. 9. 26. 17:02

출처 : 인프런 - 실전! 스프링 부트와 JPA활용1 - 웹 애플리케이션 개발(유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용  


1. 프로젝트 생성

 

(1) Project Setting

  • Gradle - Groovy
  • Java 17
  • Jar
  • Spring Boot3.3.4
  • group: jpabook
  • Artifact: jpashop

(2) Dependencies

  • Spring Data JPA
  • Spring Web
  • Thymeleaf
  • Validation - 스프링 부트 3.x.x 버전부터는 직접 추가해야함
  • Lombok
  • B2 Database

(3) build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.4'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'jpabook'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

(4) 롬복 - Hello 클래스 생성

  • 플러그인에서 Lombok 설치 확인
  • 인텔리제이 setting -> annotaion processors 검색 -> Enabld annotation processing 활성화(인텔리제이 버전이 2021년 이상 버전에서는 해당 설정을 하지 않아도 잘 동작함, 만약 동작하지 않는다면 해당 설정을 진행)
  • @Getter, @Setter 애노테이션으로 게터, 세터를 자동으로 만들어주고, 이 외에도 기본생성자, 생성자 등등 여러가지 기능을 애노테이션으로 편리하게 설정하는 기능을 제공함 - 롬복의 기능은 스프링 강의에 자세히 나와있음
@Getter
@Setter
public class Hello {
    private String data;
}

2. 라이브러리 살펴보기

1) 스프링부트 라이브러리 살펴보기

(1) spring-boot-starter-web

  • spring-boot-starter-tomcat: 톰캣(웹서버)
  • spring-webmvc: 스프링 웹MVC
  • spring-boot-starter
    • spring-boot
      • spring-core
    • spring-boot-starter-logging
      • logback, slf4j

(2) spring-boot-starter-thymeleaf: 타임리프 템플릿 엔진(View)

(3) spring-boot-stater-data-jpa

  • spring-boot-starter-aop
  • spring-boot-stater-jdbc
    • HikariCP 커넥션 풀(부트 2.0부터 기본)
  • hibernat-core: 하이버네이트 + JPA
  • spring-data-jpa: 스프링 데이터 JPA

2) 테스트 라이브러리

  • spring-boot-stater-test
    • junit: 테스트 프레임워크
    • mockito: 목 라이브러리
    • assertj: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
    • spring-test: 스프링 통합 테스트 지원

3) 핵심 라이브러리

  • 스프링 MVC
  • 스프링 ORM
  • JPA, 하이버네이트
  • 스프링 데이터 JPA: JPA를 먼저 이해하고 사용해야하는 응용 기술

4) 기타 라이브러리

  • H2 데이터베이스 클라이언트
  • 커넥션 풀: HikariCP(스프링 부트 기본)
  • WEB(thymeleaf)
  • 로깅: SFL4J & LogBack
  • 테스트

3. View 환경 설정

1) HelloController

@Controller
public class HelloController {

    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello~!~!~!~!~!~!~!~!~!~!");
        return "hello";
    }
}

2) thymeleaf 템플릿엔진 동작 확인 

(1) resources/templates/hello.html - 동적 템플릿

  • 서버를 실행하고 localhost:8080에 접속하면 HelloController에서 넘긴 값이 ${data}에 치환되어서 입력된 것을 확인할 수 있음
  • 그냥 로컬에서 hello.html을 열면 natural template으로 동작하여 기본값으로 입력해둔 안녕하세요. 손님 이라는 문구가 출력됨 - thymeleaf의 장점(마크업을 깨지 않고 파일 자체를 웹브라우저로 열어서 확인해볼 수 있음)
<!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>

 

** 참고

  • 동적 웹페이지를 수정할 일이 정말 많은데, 기본적으로는 한번 수정할때마다 웹 서버를 다시 껏다 켜야 수정한 사항이 반영되어 애플리케이션이 실행됨
  • spring-boot-devtools 라이브러리를 추가하면 html 파일을 컬파일만 해주면 서버 재시작 없이 View 파일 변경이 가능함
  • 인텔리제이 컴파일 방법: build -> Recomple
implementation 'org.springframework.boot:spring-boot-devtools'

 

(2) resources/static/index.html - 웰컴페이지(정적인 순수 html)

  • 서버 실행후 localhost:8080으로 접속하면 바로 처음에 나오는 정적 페이지를 생성
<!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>
Hello
<a href="/hello">hello</a>
</body>
</html>

4. H2 데이터베이스 설치

1) 다운로드 및 설치

  • https://www.h2database.com
  • 사이트 접속 후 스프링 부트 버전에 맞게 설치 - 스프링 부트 3.x면 2.1.214버전 이상을 사용

2) 데이터베이스 파일 생성 방법

  • 설치 후 h2데이터베이스 폴더의 bin에 있는 h2.sh파일을 실행
  • JDBC URL: jdbc:h2:~/jpashop 설정 후 연결 (최소 한번)
  • home 경로에 jpashop.mb.db 파일이 생성되었는지 확인
  • 이후 부터는 jdbc:h2:tcp://localhost/~/jpashop 으로 접속

5. JPA와 DB설정, 동작확인

1) main/resources/application.yml

# yml은 띄어쓰기에 주의해야함
spring: # 띄어쓰기 없음
  datasource: # 띄어쓰기 2칸
    url: jdbc:h2:tcp://localhost/~/jpashop # 띄어쓰기 4칸
    username: sa
    password:
    driver-class-name: org.h2.Driver

  jpa: # 띄어쓰기 2칸
    hibernate: # 띄어쓰기 4칸
      ddl-auto: create # 띄어쓰기 6칸
    properties:
      hibernate:
#        show_sql: true  # 띄어쓰기 8칸, System.out으로 출력 -> 실무에서는 사용안함
        format_sql: true  # Log로 출력

logging: # 띄어쓰기 없음
  level: # 띄어쓰기 2칸
    org.hibernate.SQL: debug # 띄어쓰기 4칸 -> JPA 쿼리 출력
#    org.hibernate.orm.jdbc.bind: trace # SQL에서 파라미터값이 ?로 나오는데 그것을 실제 값으로 출력

2) 실제 동작 확인

(1) 회원 엔터티

@Entity
@Getter @Setter
public class Member {

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

 

(2) 회원 리포지토리

  • 회원 저장 시 Member를 반환해도 되지만 커맨드(명령)과 쿼리(조회)를 분리하라는 원칙을 적용하여 유지보수성과 시스템 안정성을 높이는 설계를 적용
  • save() 메서드는 member 객체를 데이터베이스에 저장하는 역할을 하고 이 과정에서 상태 변경이 일어나는 명령 메서드이기 때문에 그결과로 상태를 반환하기 보다 식별자만 반환하도록 설계하여 데이터 저장에 집중하고 조회기능을 따로 분리함
  • find() 메서드로 save()에서 반환된 id값을 가지고 조회 기능만 하는 메서드를 생성해서 조회는 find()메서드를 통해서만 할 수 있도록하여 save()를 통해 객체를 조회 없이 조작할 수 있는 상황을 방지
  • 이렇게 각 메서드가 정해진 기능에 맞게 집중해서 사용하도록 설계되어있으면 데이터 무결성을 잘 보장할 수 있음
package jpabook.jpashop;

@Repository
public class MemberRepository {

    @PersistenceContext // 생략 가능
    private EntityManager em;

    public Long save(Member member) {
        em.persist(member); // 저장
        // 커맨드와 쿼리를 분리하라는 원칙에 의해 member를 반환하는게 아닌, member.id()만 반환해서 리턴값으로 조회할 수 있도록 설계
        return member.getId();
    }

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

 

(3) MemberRepositoryTest

  • Junit5를 사용한 테스트, Junit4를 사용했다면@ExtgendWith(SpringExtension.class)를 @Runwith(SpringRunner.class)로 입력해야함
  • 스프링 부트 2.1 이상 버전을 사용해서 빌드를 했다면 @SpringBootTest 만으로도 자동으로 SpringExtension을 추가해주기 때문에 생략해도 됨
  • 마지막 테스트가 성공하는 이유는 JPA의 영속성 컨텍스트와 1차 캐시 메커니즘 때문인데, 영속성 컨텍스트는 동일성을 보장하며 같은 영속성 컨텍스트 내에서 같은 ID를가진 엔터티는 동일한 인스턴스가 됨 -JPA 기본편에서 자세히 배움
package jpabook.jpashop;

@ExtendWith(SpringExtension.class)
@SpringBootTest
class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;

    @Test
    @Transactional  // 테스트에서 적용시 테스트가 끝나면 자동으로 롤백
//    @Rollback(false) // 롤백을 false로 설정하면 DB에 데이터가 저장됨
    public void testMember() {
        // given
        Member member = new Member();
        member.setUsername("memberA");

        // when
        Long savedId = memberRepository.save(member);
        Member findMember = memberRepository.find(savedId);

        // then
        Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
        Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        Assertions.assertThat(findMember).isEqualTo(member);
    }
}

 

(4) jar 빌드해서 동작 확인

  • 터미널에서 ~jpashop 위치로 이동 후 ./gradle clean build를 실행
  • ~/jpashop/build/libs에 생성된 jpashop-0.0.1-SNAPSHOT.jar를 실행하여 배포 실행
  • 실행 후 localhost:8080으로 접속하면 정상적으로 웹이 띄워지고 실행이 잘 됨
  • 실행 전 애플리케이션이 실행되고 있다면 (8080서버가 띄워져있다면) 끄고 다시 실행해야함
java -jar jpashop-0.0.1-SNAPSHOT.jar

 

** 참고

  • 스프링 부트를 통해 복잡한 설정이 다 자동화 되어서 persistence.xml도 없고 LocalContainerEntityManagerFactoryBean도 없음