ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SPRINGBOOT] JPA 기본 기능과 설정
    SPRINGBOOT 2022. 4. 7. 21:05

    참조 : 스프링부트와 AWS로 혼자 구현하는 웹 서비스(이동욱 저)

     

    웹 개발 내에서 데이터베이스를 다루는 일은 필수적이다.

    하지만 객체지향 프로그래밍을 추구하는 업계 내에서 개발 시간보다는 SQL에 더 시간을 쏟는 아이러니가 발생한다.

    객체 모델링이 아닌 테이블 모델링에 더 집중되고, 객체를 단순하게 테이블 구조에 맞춰서 전달하기만 하는 개발이 더 많이 이루어지고 있는 것이다.

    이러한 문제를 해결하기 위해 JPA라는 JAVA 표준 ORM(Object Relational Mapping)기술이 나오게 된 것이다.

     

    1. JPA

     

    관계형 데이터베이스는 어떻게 데이터를 저장할지에 초점이 맞춰진 기술이다.

    객체지향 프로그래밍은 기능과 속성을 한 곳에서 관리하는 기술이다.

    이 두 개념은 애초에 다른 사상에서 출발하였고 이로 인해 패러다임의 불일치가 발생한다.

     

    예를 들어,

    User user = findUser();
    Group group = user.getGroup();

    라는 코드에서는 명확하게 Group이 User의 하위, 즉 User-Group이 부모-자식 관계인 것을 알 수 있다.

    하지만 만일 여기에 데이터베이스가 추가된다면,

    User user = userDao.findUser();
    Group group = groupDao.getGroup(user.getGroupId());

    User 따로, Group 따로 데이터베이스에서 조회해와야 하기 때문에 어떤 관계를 가지고 있는지 명확히 알 수 없다.

    때문에 점점 데이터 베이스 모델링에 치우치게 되는 것이다.

     

    이러한 패러다임의 불일치를 해결시키기 위해 등장한 것이 JPA이다.

    개발자는 객체지향 프로그래밍을 하고, JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신 생성하여 실행한다.

    더이상 SQL에 종속적인 개발을 하지 않아도 되는 것이다.

    객체 중심으로 개발이 이루어지게 되면 생산성 향상, 용이한 유지보수로까지 이어질 수 있다.

     

     

    2. Spring Data JPA

     

    JPA는 인터페이스로 사용하기 위해서는 구현체가 필요하다.

    대표적으로 Hibernate, Eclipse Link 등이 있다.

     

    하지만 Spring에서는 Spring Data JPA라는 모듈을 이용하여 JPA를 다룬다.

    이유로는 

    - 구현체 교체의 용이성

    - 저장소 교체의 용이성(관계형 데이터베이스 외에 다른 저장소로 쉽게 교체하기 위함)

     

     

    3. 프로젝트에 Spring Data JPA 적용하기

     

    build.gradle의 dependencies에 다음 의존성들을 등록한다.

    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    implementation('com.h2database:h2')

     

    main > java > com > study > crystal > test 밑에 도메인을 담을 패키지를 생성한다.

    도메인이란 소프트웨어에 대한 요구사항 혹은 문제 영역이다.

    기존에 xml에 쿼리를 담고 쿼리의 결과만 담던 dao의 일들이 모두 도메인 클래스에서 해결되는 것이다.

     

    domain 패키지 밑에 다시 posts 패키지, posts클래스를 생성한다.

     

    posts 클래스에 코드를 작성한다.

    package com.study.crystal.test.domain.posts;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import javax.persistence.*;
    
    //Getter 메소드 자동 생성
    @Getter
    // 기본생성자 자동 추가
    @NoArgsConstructor
    // (Entity))테이블과 링크될 클래스임을 나타냄. 
    // 클래스의 카멜케이스 이름을 언더스코어네이밍(_)으로 테이블 이름을 매칭한다. 
    @Entity 
    public class posts {
    
        @Id //해당테이블의 PK필드를 나타냄
        //PK의 생성규칙
        @GeneratedValue(strategy = GenerationType.IDENTITY) //GenerationType.IDENTITY = auto_increment
        private Long id;
    	
        //테이블의 컬럼을 나타냄. 하지만 굳이 선언하지 않아도 필드는 모두 컬럼이 됨.(옵션 추가시 사용)
        @Column(length = 500, nullable = false)
        private String title;
    
        @Column(columnDefinition = "TEXT", nullable = false)
        private String content;
    
        private String author;
    
        // 해당 클래스의 빌더 패턴 클래스 생성
        //생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함
        @Builder
        public Posts(String title, String content, String author){
            this.title = title;
            this.content = content;
            this.author = author;
        }
    }

     

    이 클래스에는 Setter 메소드가 없다. 

    이유는 클래스의 인스턴스의 값들이 언제 어디서 변하는지 명확하게 파악할 수 없어서 

    차후 기능 변경 시 복잡해지기 때문이다.

    그래서 필드의 값 변경이 필요하다면 명확히 그 목적과 의도를 알 수 있는 메소드를 추가해야한다.

    즉, Entity 클래스에서는 절!대! Setter 메소드를 생성하지 않는다.

     

    Setter가 없다면 어떻게 값을 채워 데이터에 삽입을 할까?

    생성자나 @Builder를 통해 제공되는 빌더 클래스를 사용한다.

    둘 다 생성시점에 값을 채워주는 역할을 하지만 생성자의 경우 지금 채워야 할 필드가 무엇인지 명확히 지정할 수 없지만 빌더 클래스는 어느 필드에 어떤 값을 채워야 할지 명시가 가능하다는 것이다.

     

    Post 클래스로 데이터베이스를 접근하게 해 줄 JpaRepository 인터페이스를 생성한다.

     

    인터페이스를 생성 후 JpaRepository<Entity 클래스, PK타입> 을 상속한다.

    상속받으면 기본적인 CRUD 메소드가 자동으로 생성된다.

     

    주의할 점은 Entity 클래스와 기본 Entity Repository는 함께 위치해야한다.

    package com.study.crystal.test.domain.posts;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface PostsRepository extends JpaRepository<Posts, Long> {
    }

     

     

    4. Spring Data JPA 테스트 코드 작성하기

     

    test 디렉토리 밑에 domain.posts 패키지를 생성하고 PostRepositoryTest라는 테스트 클래스를 생성한다.

     

    테스트 코드를 작성한 후 실행해 보면 검증에 성공한다.

    package com.study.crystal.test.domain.posts;
    
    import org.junit.After;
    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.context.junit4.SpringRunner;
    
    import java.util.List;
    import static org.assertj.core.api.Assertions.assertThat;
    
    @RunWith(SpringRunner.class)
    //별다른 설정없이 SpringBootTest를 실행하면 H2데이터베이스를 자동으로 실행한다.
    @SpringBootTest
    public class PostsRepositoryTest {
        @Autowired
        PostsRepository postsRepository;
    
        @After //단위테스트가 끝날 때마다 수행되는 메소드 지정
        public void cleanup() {
            postsRepository.deleteAll();
        }
    
        @Test
        public void 게시글저장_불러오기(){
            //given
            String title = "테스트 게시글";
            String content = "테스트 본문";
    		
            //테이블 posts에 insert/update 쿼리 실행
            //id 값이 있다면 update, 없으면 insert 쿼리가 실행
            postsRepository.save(Posts.builder().title(title)
                                                .content(content)
                    			    .author("crystal")
                                                .build());
    
            //when
            //테이블 posts에 있는 모든 데이터를 조회하는 메소드
            List<Posts> postsList = postsRepository.findAll();
    
            //then
            Posts posts = postsList.get(0);
            assertThat(posts.getTitle()).isEqualTo(title);
            assertThat(posts.getContent()).isEqualTo(content);
        }
    }

     

    만약 실제로 실행된 쿼리가 어떤 형태인지 확인하기 위해서는 application.properties, application.yml등의 파일에 코드를 작성하여 설정할 수 있다.

    spring.jpa.show_sql=true

     

    이후 다시 테스트 코드를 실행시키면 다음과 같이 로그가 찍히는 것을 확인할 수 있다.

     

    이때 , create table 쿼리에 id bigint generated by default as identity 라는 옵션으로 생성된 것을 알 수 있는데

    이는 H2 문법이다.

    출력 쿼리 로그를 MySQL 버전으로 변경하려면 application.properties에 코드를 추가한다.

    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

    이후 다시 테스트 코드를 실행하면 다음과 같이 not null auto_increment로 바뀌어 로그가 찍히게 된다.

     

Designed by Tistory.