스프링

 

중급자를 위해 준비한
[웹 개발, 백엔드] 강의입니다.

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다. 스프링 데이터 JPA 실무 노하우를 전해드립니다.

✍️
이런 걸
배워요!

스프링 데이터 JPA를 기초부터 실무 활용까지 한번에 배울 수 있습니다.

실무에서 실제 사용하는 기능 위주로 학습합니다.

단순한 기능 설명을 넘어 실무 활용 노하우를 배울 수 있습니다.

JPA와 스프링 데이터 JPA의 차이를 명확하게 이해할 수 있습니다.

 

강좌 : https://www.inflearn.com/course/스프링-데이터-JPA-실전#

 

 

 

수업자료 : 

https://github.com/braverokmc79/jpa-basic-lecture-file2

 

 

 

소스 : https://github.com/braverokmc79/data-jpa

 

 

 

 

 

 

 

 

 

 

 

 

 

[1] 프로젝트 환경설정

 

 

1. 프로젝트 생성

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=27997&tab=curriculum

 

 

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.7.9'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'study'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

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

 

 

프로젝트 생성


스프링 부트 스타터(https://start.spring.io/)

Project: Gradle - Groovy Project
사용 기능: web, jpa, h2, lombok


SpringBootVersion: 2.7.9

groupId: study
artifactId: data-jpa


주의! - 스프링 부트 3.0
스프링 부트 3.0을 선택하게 되면 다음 부분을 꼭 확인해주세요.


1. Java 17 이상을 사용해야 합니다.


2. jakarta  패키지 이름을 jakarta 로 변경해야 합니다.


오라클과 자바 라이센스 문제로 모든 jakarta 패키지를 jakarta로 변경하기로 했습니다.

3. H2 데이터베이스를 2.1.214 버전 이상 사용해주세요.

 


패키지 이름 변경 예)

JPA 애노테이션

javax.persistence.Entity jakarta.persistence.Entity

 

스프링에서 자주 사용하는 @PostConstruct 애노테이션

javax.annotation.PostConstruct jakarta.annotation.PostConstruct

 

 

스프링에서 자주 사용하는 검증 애노테이션

javax.validation jakarta.validation

 

 

 

=> 빌드를 인텔리제이로 변경

셋팅 -> build 검색 후  --> 그래들 build  -> 설정 

 

=> 롬복 적용

 

=> 인텔리제이 스프링부트 자동빌드 적용

 

 

 

 

동작 확인


기본 테스트 케이스 실행


스프링 부트 메인 실행 후 에러페이지로 간단하게 동작 확인(`http://localhost:8080')
테스트 컨트롤러를 만들어서 spring web 동작 확인(http://localhost:8080/hello)

 

 

 

테스트 컨트롤러

 

package study.datajpa.controller;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return  "hello";
    }




}

 

 

참고: 최근 IntelliJ 버전은 Gradle로 실행을 하는 것이 기본 설정이다. 이렇게 하면 실행속도가 느리다.
다음과 같이 변경하면 자바로 바로 실행하므로 좀 더 빨라진다.

>
> Preferences Build, Execution, Deployment Build Tools Gradle
> Build and run using: Gradle IntelliJ IDEA
> Run tests using: Gradle IntelliJ IDEA

 

 

 

 

롬복 적용
1. Preferences plugin lombok 검색 실행 (재시작)
2. Preferences Annotation Processors 검색 Enable annotation processing 체크 (재시작)
3. 임의의 테스트 클래스를 만들고 @Getter, @Setter 확인

 

 

 

 

 

 

 

 

 

 

 

2. 프로젝트 생성

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=27998&tab=curriculum

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3. H2 데이터베이스 설치

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=27999&tab=curriculum

 

 

 

H2 데이터베이스 설치

개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공
https://www.h2database.com


다운로드 및 설치
h2 데이터베이스 버전은 스프링 부트 버전에 맞춘다.

권한 주기: chmod 755 h2.sh


데이터베이스 파일 생성 방법
jdbc:h2:~/datajpa (최소 한번)

~/datajpa.mv.db 파일 생성 확인

이후 부터는 jdbc:h2:tcp://localhost/~/datajpa 이렇게 접속


> 참고: H2 데이터베이스의 MVCC 옵션은 H2 1.4.198 버전부터 제거되었습니다. 사용 버전이 1.4.199
이므로 옵션 없이 사용하면 됩니다.

 

 

 

 

 

 

 

 

 

 

4. 스프링 데이터 JPA와 DB 설정, 동작확인

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28000&tab=curriculum

 

application.properties

#변환 사이트 https://mageddo.com/tools/yaml-converter
#코드 색깔 표시
spring.output.ansi.enabled=always


#Springboot auto build
spring.devtools.livereload.enabled=true
spring.devtools.restart.enabled=true


spring.driver-class-name=org.h2.Driver
spring.datasource.url= jdbc:h2:tcp://localhost/~/datajpa
spring.datasource.username=sa
spring.datasource.password=

#spring.jpa.properties.hibernate.show_sql=true
spring.jpa.hibernate.ddl-auto= create
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.default_batch_fetch_size=100



logging.level.org.hibernate.SQL=debug

 

 

 

application.yml

spring:
  datasource:
   url: jdbc:h2:tcp://localhost/~/datajpa
   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
# org.hibernate.type: trace

 

 

 

 

spring.jpa.hibernate.ddl-auto: create
이 옵션은 애플리케이션 실행 시점에 테이블을 drop 하고, 다시 생성한다.

 

 

> 참고: 모든 로그 출력은 가급적 로거를 통해 남겨야 한다.
> show_sql : 옵션은 System.out 에 하이버네이트 실행 SQL을 남긴다.
> org.hibernate.SQL : 옵션은 logger를 통해 하이버네이트 실행 SQL을 남긴다

 

 

 

실제 동작하는지 확인하기

 

 

회원 엔티티

package study.datajpa.entity;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;

    protected Member() {
    }

    public Member(String username){
        this.username=username;
    }

    public void changeUsername(String username){
        this.username=username;
    }

}

 

 

1) 회원 JPA 리포지토리

package study.datajpa.repository;

import org.springframework.stereotype.Repository;
import study.datajpa.entity.Member;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Repository
public class MemberJpaRepository {

    @PersistenceContext
    private EntityManager em;

    public Member save(Member member){
        em.persist(member);
        return  member;
    }

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




}

 

 

JPA 기반 테스트

package study.datajpa.repository;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import study.datajpa.entity.Member;

@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberJpaRepositoryTest {

    @Autowired
    MemberJpaRepository memberJpaRepository;


    @Test
    public void testMember(){
        Member member =new Member("memberA");
        Member saveMember = memberJpaRepository.save(member);

        Member findMember = memberJpaRepository.find(saveMember.getId());

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



}

 

 

 

 

 

2) 스프링 데이터 JPA 리포지토리

 

package study.datajpa.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Member;

public interface MemberRepository extends JpaRepository<Member, Long> {


}

 

 

스프링 데이터 JPA 기반 테스트

package study.datajpa.repository;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import study.datajpa.entity.Member;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {

    @Autowired
    MemberRepository memberRepository;

    @Test
    public void testMember(){
        Member member =new Member("memberA");
        Member saveMember = memberRepository.save(member);

        Member findMember = memberRepository.findById(saveMember.getId()).get();

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

}

 

 

 

Entity, Repository 동작 확인
jar 빌드해서 동작 확인

 

 

참고: 스프링 부트를 통해 복잡한 설정이 다 자동화 되었다. persistence.xml 도 없고, LocalContainerEntityManagerFactoryBean 도 없다.

스프링 부트를 통한 추가 설정은 스프링 부트 메뉴얼을 참고하고, 스프링 부트를 사용하지 않고 순수 스프링과 JPA 설정 방법은

자바 ORM 표준 JPA 프로그래밍 책을 참고하자.

 

 

 

쿼리 파라미터 로그 남기기
로그에 다음을 추가하기 org.hibernate.type : SQL 실행 파라미터를 로그로 남긴다.
외부 라이브러리 사용
https://github.com/gavlyukovskiy/spring-boot-data-source-decorator

 

 

스프링 부트를 사용하면 이 라이브러리만 추가하면 된다

 

implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.7'

 

 

> 참고: 쿼리 파라미터를 로그로 남기는 외부 라이브러리는 시스템 자원을 사용하므로,

개발 단계에서는 편하게 사용해도 된다. 하지만 운영시스템에 적용하려면 꼭 성능테스트를 하고 사용하는 것이 좋다.

 

 

 

★쿼리 파라미터 로그 남기기 - 스프링 부트 3.0

p6spy-spring-boot-starter 라이브러리는 현재 스프링 부트 3.0을 정상 지원하지 않는다.

스프링 부트 3.0에서 사용하려면 다음과 같은 추가 설정이 필요하다.

 

 

1. org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일 추가

src/resources/META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports

 

com.github.gavlyukovskiy.boot.jdbc.decorator.DataSourceDecoratorAutoConfiguration

 

폴더명: src/resources/META-INF/spring
파일명: org.springframework.boot.autoconfigure.AutoConfiguration.imports

 

 

2. spy.properties 파일 추가

src/resources/spy.properties

 

appender=com.p6spy.engine.spy.appender.Slf4JLogger

 

이렇게 2개의 파일을 추가하면 정상 동작한다

 

 

메이븐 설정

		<dependency>
			<groupId>com.github.gavlyukovskiy</groupId>
			<artifactId>p6spy-spring-boot-starter</artifactId>
			<version>1.9.0</version>
		</dependency>

 

 

 

 

 

 

 

 

 

[2] 예제 도메인 모델

 

 

5. 예제 도메인 모델과 동작확인

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28002&tab=curriculum

 

 

 

 

Member 엔티티

package study.datajpa.entity;

import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username" , "age"})
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    public Member(String username){
        this.username=username;
    }

    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        if(team!=null){
            changeTeam(team);
        }
    }

    public void changeUsername(String username){
        this.username=username;
    }


    public void changeTeam(Team team){
        this.team =team;
        team.getMembers().add(this);
    }






}

 

 

롬복 설명


@Setter: 실무에서 가급적 Setter는 사용하지 않기


@NoArgsConstructor AccessLevel.PROTECTED: 기본 생성자 막고 싶은데, JPA 스팩상 PROTECTED로 열어두어야 함



@ToString은 가급적 내부 필드만(연관관계 없는 필드만)


changeTeam() 으로 양방향 연관관계 한번에 처리(연관관계 편의 메소드)

 

 

 

 

Team 엔티

 

package study.datajpa.entity;

import lombok.*;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"})
public class Team {

    @Id
    @GeneratedValue
    @Column(name="team_id")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members =new ArrayList<>();


    public Team(String name) {
        this.name = name;
    }
}

 

 

Member와 Team은 양방향 연관관계, Member.team 이 연관관계의 주인, Team.members 는 연관관계의


주인이 아님, 따라서 Member.team 이 데이터베이스 외래키 값을 변경, 반대편은 읽기만 가능

 

 

 

데이터 확인 테스트

 

package study.datajpa.entity;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberTest {

    @PersistenceContext
    EntityManager em;


    @Test
    public void testEntity(){
        Team teamA =new Team("teamA");
        Team teamB =new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        Member member1 =new Member("member1", 10,teamA);
        Member member2 =new Member("member2", 20,teamA);
        Member member3 =new Member("member3", 30,teamB);
        Member member4 =new Member("member4", 40,teamB);

        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);

        //초기화
        em.flush();
        em.clear();

        List<Member> members=em.createQuery("select m from Member m ", Member.class).getResultList();
        
        for(Member member: members){
            System.out.println("member = " + member);
            System.out.println("member.getTeam() = " + member.getTeam());
        }
    }


}

 

 

가급적 순수 JPA로 동작 확인 (뒤에서 변경)
db 테이블 결과 확인
지연 로딩 동작 확인

 

 

select team0_.team_id as team_id1_1_0_, team0_.name as name2_1_0_ from team team0_ where team0_.team_id in (1, 2);
member.getTeam() = Team(id=1, name=teamA)
member = Member(id=2, username=member2, age=20)
member.getTeam() = Team(id=1, name=teamA)
member = Member(id=3, username=member3, age=30)
member.getTeam() = Team(id=2, name=teamB)
member = Member(id=4, username=member4, age=40)
member.getTeam() = Team(id=2, name=teamB

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

누구나 과오를 저질러가면서 여러 가지 일을 터득해나가는 법이다. 과오가 많을수록 그 사람은 이전보다 나아진다. 그만큼 새로운 경험을 많이 해보았기 때문이다. 한 번도 실책이 없었던 사람, 그것도 큰 잘못을 저질러보지 못한 사람을 최상급의 직책으로 승진시키는 따위의 일은 없어야 한다. 왜냐하면 그런 사람은 대부분 무사안일주의로 지내온 사람이기 쉽상이기 때문이

댓글 ( 4)

댓글 남기기

작성