하위 태스크 1

JPA 의존성 확인

build.gradle에 JPA 관련 의존성 확인

build.gradle 파일에서 JPA와 데이터베이스 의존성을 확인한다. 본 실습은 H2 데이터베이스를 사용한다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'com.h2database:h2'
}

하위 태스크 2

application.properties 설정

JPA 및 데이터베이스 연결 설정

application.properties 파일에 JPA 설정을 추가한다.

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.highlight_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

하위 태스크 3

Member 엔티티 클래스 생성

@Entity, @Table, @Id, @Column 어노테이션 적용

model/Member.java 파일에 Member 엔티티 클래스를 작성한다.

@Entity
@Table(name = "member")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    @Column(nullable = false, length = 128)
    private String name;
 
    @Column(nullable = false, unique = true, length = 256)
    private String email;
}

Member 클래스에 적용된 JPA 관련 애너테이션의 의미는 다음과 같다.

  • @Entity: 클래스가 JPA 엔티티임을 선언한다. 클래스는 데이터베이스 테이블과 1:1로 매핑된다.
  • @Table: 매핑할 테이블 정보를 설정한다. 지정한 테이블의 이름은 member다.
  • @Id: 해당 필드가 테이블의 기본 키임을 나타낸다.
  • @GeneratedValue: 기본 키 생성 전략을 설정한다. IDENTITY는 데이터베이스가 번호를 자동으로 생성한다.
  • @Column: 필드와 컬럼을 매핑한다. nullable, length, unique 등의 제약 조건을 설정할 수 있다.

하위 태스크 4

테이블 자동 생성 확인

애플리케이션 실행 후 테이블 생성 확인

애플리케이션 실행 후 출력되는 로그에서 member 테이블 생성을 확인할 수 있다.

[Hibernate] 
    create table member (
        id bigint generated by default as identity,
        name varchar(128) not null,
        email varchar(256) not null unique,
        primary key (id)
    )

하위 태스크 5

MemberRepository 인터페이스 생성

JpaRepository 상속

repository/MemberRepository.javaJpaRepository 인터페이스를 상속하는 MemberRepository 인터페이스를 작성한다.

public interface MemberRepository extends JpaRepository<Member, Long> {
}

하위 태스크 6

save() 메서드 테스트

INSERT/UPDATE 작업 테스트

JpaApplication.java 파일에 ApplicationRunner 인터페이스를 구현한 JpaApplication 클래스를 작성한다.

@Component
@Slf4j
@RequiredArgsConstructor
public class JpaApplication implements ApplicationRunner {
    private final MemberRepository memberRepository;
    private final ArticleRepository articleRepository;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        var memberOne = Member.builder()
            .name("홍길동")
            .email("[email protected]")
            .build();
        var memberTwo = Member.builder()
            .name("김철수")
            .email("[email protected]")
            .build();
 
        // 홍길동 회원 삽입
        memberRepository.save(memberOne);
 
        // 김철수 회원 삽입
        memberRepository.save(memberTwo);
 
        // 김철수 회원의 이름을 김정수로 수정
        memberTwo.setName("김정수");
        memberRepository.save(memberTwo);
    }
}

하위 태스크 7

findById(), findAll() 테스트

SELECT 작업 테스트

findById 메서드와 findAll 메서드를 활용한 코드를 추가한다.

@Component
@Slf4j
@RequiredArgsConstructor
public class JpaApplication implements ApplicationRunner {
    private final MemberRepository memberRepository;
    private final ArticleRepository articleRepository;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // ...
        var members = memberRepository.findAll();
        log.info("{}", members);
        
        var foundMember = memberRepository.findById(2L);
        log.info("{}", foundMember);
    }
}

애플리케이션을 실행한 결과는 다음과 같다.

[Member(id=1, name=홍길동, [email protected]), Member(id=2, name=김정수, [email protected])]
Optional[Member(id=2, name=김정수, [email protected])]

하위 태스크 8

deleteById() 테스트

DELETE 작업 테스트

하위 태스크 7의 코드에 deleteById 메서드를 사용한 코드를 추가한다.

@Component
@Slf4j
@RequiredArgsConstructor
public class JpaApplication implements ApplicationRunner {
    private final MemberRepository memberRepository;
    private final ArticleRepository articleRepository;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // ...
        memberRepository.deleteById(2L);
 
        var membersAfterDeleteOne = memberRepository.findAll();
        log.info("{}", membersAfterDeleteOne);
    }
}

애플리케이션 실행 결과는 다음과 같다.

[Member(id=1, name=홍길동, [email protected])]

하위 태스크 9

Article 엔티티 생성

@ManyToOne 연관관계 설정

model/Article.java 파일에 Article 클래스를 작성한다.

@Entity
@Table(name = "article")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    private String content;
 
    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;
}

하위 태스크 10

Member 엔티티에 @OneToMany 추가

양방향 연관관계 설정

Member 클래스에 @OneToMany 애노테이션이 적용된 articles 필드를 추가해 양방향 연관 관계를 설정한다.

@Entity
@Table(name = "member")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    // ...
 
    @Builder.Default
    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<Article> articles = new ArrayList<>();
}

상단 코드의 @OneToMany 애노테이션은 Article 쿨래스의 member 필드에 의해 관계가 관리되고, 부모 엔티티에게 가해지는 작업이 자식 엔티티에게도 전달되도록 설정한다.

하위 태스크 11

연관관계를 통한 데이터 저장

cascade를 활용한 데이터 저장

JpaApplcation 클래스의 run 메서드를 다음과 같이 변경한다.

Member member = Member.builder().name("홍길동").email("[email protected]").build();
Article article = Article.builder().title("JPA에 관하여").content("JPA는 멋져요.").build();
article.setMember(member);
member.getArticles().add(article);

애플리케이션을 실행하고 로그를 확인한 결과는 다음과 같다. article 테이블에 외래 키가 생성되고, INSERT 문이 준비된 것을 볼 수 있다.

[Hibernate] 
    alter table if exists article 
       add constraint FK6l9vkfd5ixw8o8kph5rj1k7gu 
       foreign key (member_id) 
       references member
[Hibernate] 
    /* insert for
        com.example.demo.model.Member */insert 
    into
        member (email, name, id) 
    values
        (?, ?, default)
[Hibernate] 
    /* insert for
        com.example.demo.model.Article */insert 
    into
        article (content, member_id, title, id) 
    values
        (?, ?, ?, default) 

하위 태스크 12 ~ 13

커스텀 쿼리 메서드 작성

메서드 이름으로 쿼리 자동 생성

다양한 쿼리 메서드 패턴 실습

Containing, Like 등 조건 사용

MemberRepsitory 인터페이스에 세 개의 메서드 서명을 추가한다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByName(String name);
    List<Member> findByEmailContaining(String email);
    Optional<Member> findByEmail(String email);
}

JpaApplication 클래스의 run 메서드를 다음과 같이 수정한다.

memberRepository.save(Member.builder().name("홍길동").email("[email protected]").build());
memberRepository.save(Member.builder().name("김철수").email("[email protected]").build());
 
log.info("{}", memberRepository.findByName("홍길동"));
log.info("{}", memberRepository.findByEmail("[email protected]"));
log.info("{}", memberRepository.findByEmailContaining("kim"));

애플리케이션을 실행한 결과는 다음과 같다.

[com.example.demo.model.Member@50aa9a91]
Optional[com.example.demo.model.Member@1e34840f]
[com.example.demo.model.Member@1c512bb]