스프링 부트 프로젝트 시작할 때, 몰랐던 것 - JPA(1)
이동욱 님의 책 "스프링 부트와 AWS로 혼자 구현하는 웹 서비스"를 보면서 기본적인 것들, 팁들을 많이 알았다. 이글의 출처이다.
JPA는 자바 표준 ORM 기술이다.
웹 애플리케이션에서는 거의 관계형 데이터베이스가 필수이다. 따라서 객체를 관계형 데이터베이스에서 관리하는 것이 중요하다.
하지만 객체지향 언어를 사용한다면 여러 문제가 생긴다.
1. 프로젝트의 대부분이 코드보다 SQL이 가득해짐.
-> 관계형 데이터베이스는 SQL로만 조작할 수 있다 보니, CURD에 대한 SQL을 매번 생성해야 한다.(했다.)
2. 패러다임 불일치.
-> RDB는 어떻게 데이터를 저장할지에 초점이 맞춰진 기술이고
-> 객체지향 프로그래밍 언어는 기능과 속성을 한 곳에서 관리하는 기술이다.
-> 이 둘의 시작점 조차 다르다. 객체를 RDB에 저장하려니 문제가 생겼다.(패러다임의 불일치)
코드를 줄여주고, 이 둘의 중간에서 패러다임을 일치시켜주는 기술이 JPA다.
JPA를 사용하면 개발자는 객체지향적으로 프로그래밍할 수 있고, JPA는 이에 맞게 sql을 실행한다. -> sql 종속적인 개발에서 벗어난다.
JPA는 인터페이스이다. 구현체는 대표적으로 Hibernate가 있다. 구현체들을 더 추상화시킨 것이 Spring Data JPA인데 이것을 사용해 JPA 기술을 다룬다.(Spring 진영에서 권장하는 방법)
JPA <- Hibernate <- Spring Data JPA(JPA라는 인터페이스를 구현한 것이 Hibernate(구현체)이고, 그것을 추상화시킨 것이 Spring Data JPA다.)
Spring Data JPA가 등장한 이유
1. 구현체 교체를 위해
-> Spring Data JPA 내부에서 구현체 매핑을 지원해주기 때문에
2. 저장소 교체를 위해
-> RDB 외에 다른 저장소로 쉽게 교체할 수 있다.
-> RDB가 감당 못할 정도의 트래픽이 발생한다면 MongoDB(NoSQL)로 바꾸는 경우가 생기는데 이때 Spring Data JPA를 Spring Data MongoDB로 의존성만 교체해주면 된다.
-> Spring Data 하위 프로젝트들은 기본적인 CRUD 인터페이스가 같기 때문에 가능하다. Spring Data JPA, Spring Data Redis, Spring Data MongoDB 등등 같은 인터페이스를 가지고 있기 때문이다.
-> 따라서 Hibernate를 직접 사용하기보다, Spring Data를 사용하는 것을 권장하고 있다.
JPA를 사용하려면 객체지향 프로그래밍과 RDB 모두 잘 이해해야 한다.
JPA를 사용하기 build.gradle에 의존성을 추가해야 한다.
spring-boot-starter-data-jpa : 스프링 부트용 Spring Data JPA 추상화 라이브러리.
-> 스프링 부트 버전에 맞춰 자동으로 JPA 관련 라이브러리 버전을 관리해줌.
com.h2database:h2 : 인메모리형 RDB. 별도의 설치 없이 프로젝트 의존성만으로 관리할 수 있음.
-> 대신 메모리에서 실행되기 때문에, 애플리케이션을 재시작할 때마다 초기화됨. 그래서 테스트 용도로 많이 쓰임.
domain : 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역. 이곳에 애플리케이션에서 사용되는 데이터? 같은 것을 보관한다.
애플리케이션에서 사용될 Entity 클래스(데이터)를 만든다.
클래스에 어노테이션을 붙일 때, 여러 어노테이션을 붙이는 경우가 있는데 이럴 경우에는 주요 어노테이션을 클래스에 가깝게 두는 것이 좋다. 예를 들어 롬복 어노테이션은 필수가 아니기 때문에, 멀리 둬도 괜찮고, @Entity 같은 JPA 어노테이션은 가깝게 두는 것이 좋다.
@Entity : JPA 어노테이션. 테이블과 연결될 클래스임을 나타냄
기본값으로는 클래스 이름을 카멜 케이스로 하면, 테이블에서는 언더스코어 네이밍으로 이름을 매칭 한다.
@Id : PK 필드임을 나타냄.
* 참고 : PK는 Long 타입, auto_increment를 추천.
@GeneratedValue(strategy = GenerationType.IDENTITY) : PK의 생성 규칙을 나타냄
GenerationType.IDENTITY은 auto_increment가 되게 한다.
@Column : 테이블의 칼럼임을 나타냄. 굳이 붙이지 않아도 클래스의 모든 필드는 모두 칼럼이 된다.
사용하는 이유는 기본값 외에 추가로 변경이 필요한 옵션을 사용하기 위해서다.
@Builder : 해당 클래스의 빌더 패턴 클래스를 생성. 생성자에 붙으면 포함된 필드만 빌더에 포함.
* 롬복을 사용하면 테이블이 변경되더라도 수정을 최소화할 수 있다.
*** Entity 클래스에서는 절대 Setter 메서드를 만들지 않는다.
-> Setter 메서드가 있다면 해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 복잡해진다.
-> 대신 변경이 필요하다면, 명확히 목적과 의도를 나타낼 수 있는 메서드를 추가해야 한다.
-> 기본적인 구조는 처음에 생성자를 통해 삽입하는 것이다. 변경이 필요하면, 해당 변경에 맞는 public 메서드를 호출하여 변경하는 것을 전제로 한다.
-> 빌더 클래스 생성자를 사용하는 이유도 그렇다. 빌더 클래스를 사용하면 어느 필드에 어떤 값을 채워야 할지 명확하게 인지할 수 있다.
데이터베이스에 접근하는 JpaRepository를 만든다.
JpaRepository : 마이바티스에서 Dao라고 불리는 DB Layer 접근자. Jpa에선 Repository라고 부르고, 인터페이스로 생성한다.
인터페이스를 생성 후 JpaRepository<Entity 클래스, PK 타입>을 상속하면 기본적인 CRUD 메서드가 자동으로 생성된다.
@Repository를 추가할 필요도 없다. 주의할 점은 엔티티 클래스와 엔티티 레포지터리는 함께 위치해야 한다.
-> 밀접한 관계이고 엔티티 클래스는 기본 레포지터리 없이는 제대로 역할을 할 수가 없기 때문에
프로젝트 규모가 커져 프로젝트를 분리해야 하면, 함께 움직여야 하기 때문에 함께 관리한다.
테스트 코드를 작성하자. 이외 다른 세팅도 하자.
@AfterEach : 단위 테스트가 끝날 때마다 수행되는 메소드를 지정하는 어노테이션.
-> 보통 전체 테스트를 시작할 때, 테스트 간의 데이터 침범을 막기 위해 사용한다.
postsRepository.save : posts 테이블에 insert/update 쿼리를 날린다. 해당 객체의 id가 존재하면 update, 아니면 insert 쿼리를 날린다.
postsRepository.findAll : posts 테이블에 존재하는 모든 데이터를 조회한다.
@SpringBootTest 를 사용하면 H2 데이터베이스를 자동으로 실행한다.
콘솔 창에 실제 실행되는 쿼리를 로그로 나타내기 위해 설정을 해야 한다. 각각 main, test 하위에 resources 패키지가 있는데 거기에 application.properties 라는 파일을 만들고 다음 설정을 추가한다.