[그린컴퓨터] Server/Spring

엔티티클래스와 JPA Repository 이해하기 { Memo 엔티티 예제, JpaRepository 인터페이스 }

Ben의 프로그램 2023. 7. 11. 12:01
728x90
엔티티 클래스와 JpaRepository

실습을 통해서 실제 Spring Data JPA를 어떤 방식으로 사용하게 되는지 알아보겠습니다. 우선 Spring Data JPA 는 Hibernate 라는 JPA 구현체를 Spring boot 에서 손쉽게 사용할 수 있도록 Spring 프레임워크가 제공하는 다양한 호환성을 위한 라이브러리 중 하나라고 했습니다. 이런 Spring Data JPA를 사용하기 위해서는 2가지 준비물이 필요합니다. 첫 번째는 엔티티 클래스입니다. 엔티티 클래스는 JPA를 통해서 관리하게 되는 객체를 의미합니다. 두 번째는 Repository 입니다. Repository 는 엔티티 객체들을 처리하는 기능을 가지고 있습니다. Repository 는 Spring Data JPA 에서 제공하는 인터페이스로 스프링 내부에서 자동으로 객체를 생성하고 실행하는 구조라 개발자 입장에서는 단순히 인터페이스를 하나 정의하는 것이 끝인 아주 간단한 작업입니다. (기존 Hibernate 는 모든 코드를 직접 구현하고 트랜잭션 처리가 필요했지만, Spring Data JPA는 자동으로 생성되는 코드를 이용하므로 단순 CRUD나 페이지 처리 등의 개발에 코드를 개발하지 않아도 됩니다.)

 

JPA 사용을 위한 사전 설정

JPA 를 사용하기 위해서 스프링에서 설정해야 하는 것들이 몇 가지 있다. 자동으로 필요한 테이블을 생성하거나 JPA를 이용할 때 발생하는 SQL 등을 확인하기 위해서는 약간의 추가 설정이 필요하다. 첫 번째, spring.jpa.hibernate.ddl-auto 는 프로젝트 실행 시에 자동으로 DDL(create, alter, drop 등)을 생성할 것인지를 결정하는 설정입니다. 설정값은 create, update, create-drop, validate 가 있습니다. create 는 매번 테이블 생성을 새로 합니다. update 는 테이블이 없는 경우 create로 변경되어 테이블을 생성하고 있는 경우 alter 로 변경됩니다. 두 번째 spring.jpa.hibernate.format_sql 은 실제 JPA의 구현체인 Hibernate 가 동작하면서 발생하는 SQL을 포맷팅해서 출력합니다. 실행되는 SQL의 가독성을 높여 줍니다. 세 번째 spring.jpa.show-sql 은 JPA 처리 시에 발생하는 SQL을 보여줄 것인지를 결정합니다. 이렇게 3가지 항목을 추가한 후 프로젝트를 실행하면 데이터베이스에 어떤 테이블이 생성되는지 로그를 통해서 확인할 수 있습니다. 실행은 Boot Dashboard 를 통해서 프로젝트를 실행시키면 됩니다.

Boot Dashboard 는 내장된 Tomcat 을 실행시켜주는 역할을 합니다.

이런 식으로 말이죠. 실행된 DDL을 보면 엔티티 클래스의 @Table의 name 속성값과 동일하게 테이블 이름이 지정된 것을 볼 수 있습니다. @Table 의 name 속성값이 없는 경우에는 클래스의 이름과 동일한 이름으로 테이블이 생성됩니다. 클래스의 이름이 길고 카멜 케이스로 작성되어 있다면 _ 를 이용해서 테이블 명이 작성되게 됩니다. 

실행을 시켰다면 실제로 테이블이 설정한대로 잘 생성되었는지 확인해야합니다. 

 

 

엔티티 클래스 예제 (Memo 엔티티 클래스 만들기)

실습을 진행해보겠습니다.

Memo 클래스는 엔티티 클래스로 마치 데이터베이스의 테이블과 같은 구조를 작성합니다. 

 

@Entity

엔티티 클래스는 Spring Data JPA 를 사용하기 위해서 @Entity 라는 어노테이션을 추가해야만 합니다. @Entity 는 해당 클래스가 엔티티를 위한 클래스이며, 해당 클래스의 인스턴스들이 JPA로 관리되는 엔티티 객체라는 것을 의미합니다. 또한 @Entity 가 붙은 클래스는 옵션에 따라서 자동으로 테이블을 생성할 수도 있습니다. 이 경우 @Entity 가 있는 클래스의 멤버 변수에 따라서 자동으로 칼럼들도 생성됩니다. 

 

@Table

@Entity 어노테이션과 같이 사용할 수 있는 어노테이션으로 말 그대로 데이터베이스상에서 엔티티 클래스를 어떠한 테이블로 생성할 것인지에 대한 정보를 담기 위한 어노테이션입니다. 예를 들어 @Table(name = "t_memo")와 같이 지정하는 경우에는 생성되는 테이블의 이름이 't_memo' 테이블로 생성됩니다. 단순히 테이블의 이름뿐만 아니라 인덱스, 카탈로그, 스키마 등의 설정도 가능합니다. 

 

@Id 와 @GeneratedValue

@Entity 가 붙은 클래스는 Primary Key (이하 PK)에 해당하는 특정 필드를 @Id 로 지정해야만 합니다. @Id 즉 PK 가 사용자(클라이언트)가 입력하는 값을 사용하는 경우가 아니라면 자동으로 생성되는 번호를 사용하기 위해서 @GeneratedValue 라는 어노테이션을 활용하게 됩니다. @GeneratedValue(strategy = GenerationType.IDENTITY) 부분은 PK 를 자동으로 생성하고자 할 때 사용합니다(키 생성 전략이라고 합니다.). 만일 연결되는 데이터베이스가 오라클이면 별도의 번호를 위한 테이블이 생성되고, MySQL이나 MariaDB면 'auto increment'를 기본으로 사용해서 새로운 레코드가 기록될 때마다 다른 번호를 가질 수 있도록 처리됩니다. 

 

@Column

만일 추가적인(PK를 제외한) 필드(칼럼)가 필요하다면 @Column 어노테이션을 사용합니다. @Column 어노테이션을 이용해서 다양한 속성을 지정할 수 있습니다. 주로 nullable, name, legth 등을 이용하여 다양한 속성을 지정할 수 있게 됩니다. columnDefinition 을 이용하면 기본값을 지정할 수도 있습니다. 

 

엔티티 클래스 예제 (Memo 엔티티 클래스 만들기) 수정하기

위에서 배운 어노테이션들과 속성들을 이용해서 만들었던 Memo 엔티티 클래스를 수정해보겠습니다. 

변경된 Memo 클래스는 Lombok의 @Getter 를 이용해서 Getter 메서드를 생성하고 @Builder 를 이용해서 객체를 생성할 수 있게 처리합니다. @Builder 를 이용하기 위해서는 @AllArgsConstructor 와 @NoArgsConstructor 를 항상 같이 처리해야 컴파일 에러가 발생하지 않습니다. 

 

@Builder

@Builder 어노테이션을 간단히 이해하면 클래스의 모든 요소들에 대한 편리한 생성자 기능을 쓸 수 있다라고 이해할 수 있다. 무슨 말인지 이해하기가 어려울 텐데, 다음 코드를 살펴보자. 

Memo.builder( ) 를 하면 Memo 객체를 생성하기 위한 준비를 시킨다고 이해할 수 있습니다. 현제 Memo 클래스 객체는 mno 와 memoText 를 멤버로 갖고 있습니다. 그런 다음 코드를 보면 .memoText("~") 로 생성할 때 memoText 멤버를 초기화 시킨 것을 볼 수 있는데 mno 멤버 변수에 대해서는 초기화를 하지 않고 생성하고 있습니다. 원래 생성자를 사용할 때 초기화를 시키지 않을 멤버 변수가 있더라도 빈 값을 넣어주기라도 해야 하는데, @Builder 를 활용하면 자동으로 빈 값을 넣어주기 때문에 내가 원하는대로 편리하게 생성자를 사용할 수 있게 됩니다. 마지막에 .build( ) 로 생성한 객체를 반환시켜주면서 코드가 마무리 되게 됩니다. 

 

@Builder 어노테이션에 대한 보다 자세한 설명은 다음 블로그의 포스트를 참고하자.

https://velog.io/@park2348190/Lombok-Builder%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC

 

JpaRepository 인터페이스

앞에서 언급했듯이 Spring Data JPA 는 JPA의 구현체인 Hibernate를 이용하기 위한 여러 API를 제공합니다. 그 중에서 개발자가 가장 많이 사용할 것이 바로 JpaRepository 라는 인터페이스입니다. Spring Data JPA에는 여러 종류의 인터페이스의 기능을 통해서 JPA 관련 작업을 별도의 코드 없이 처리할 수 있게 지원합니다. 예를 들어 CRUD 작업이나 페이징, 정렬 등의 처리도 인터페이스의 메서드를 호출하는 형태로 처리하는데 기능에 따라서 상속 구조로 추가적인 기능을 제공합니다. 다만, 복잡한 조건검색은 별도의 작업이 추가로 필요합니다

(상위)Repository <- CrudRepository <- PagingAndSortRepository <- JpaRepository(하위) 순으로 상속을 받습니다. 일반적인 기능만을 사용할 때는 CrudRepository 를 사용하는 것이 좋고, 모든 JPA 관련 기능을 사용하고 싶을 때는 JpaRepository 를 이용합니다만 특별한 경우가 아니라면 JpaRepository 를 이용하는 것이 가장 무난한 선택입니다. 

 

JpaRepository 사용 예제 : Memo Entity 와 Memo Repository & MemoRepositoryTest 작성해보기

우선 Memo Entity 입니다. Entity 임을  JPA 가 알기 위해서 @Entity 어노테이션을 활용했습니다. 실제 DB에 저장될 테이블명을 지정하기 위해 @Table(name = " ") 을 사용했습니다. Lombok 의 기능인 @Getter, @Setter, @ToString, @Builder, @AllArgsConstructor, @NoArgsConstructor 를 사용하여 가독성을 향상 시켰습니다. Memo Entity 는 Entity 이기 때문에 필수적으로 PK를 가져야 하므로 @Id 어노테이션을 사용하여 PK를 생성해주었고 기본 생성전략으로 Auto_Increment 를 사용하기 위해서 @GeneratedValue 를 사용하였습니다. PK를 제외한 또 다른 컬럼을 추가하기 위해 @Column 을 사용하였으며 length 와 nullable 속성을 사용하여 길이 200, NULL 가능 여부를 지정해주었습니다.

 

JpaRepository 는 인터페이스이고, Spring Data JPA 는 이를 상속하는 인터페이스를 선언하는 것만으로도 모든 처리가 끝나는 마법 같은 일이 벌어집니다. 실제 동작 시에는 스프링이 내부적으로 해당 인터페이스에 맞는 코드를 생성하는 방식을 이용합니다. 프로젝트 내에 repository 패키지를 생성하고, MemoRepository 인터페이스를 추가합니다. 

작성된 MemoRepository 는 특이하게도 인터페이스 자체이고 JpaRepository 인터페이스를 상속하는 것만으로 모든 작업이 끝나게 됩니다. JpaRepository 를 사용할 때는 엔티티의 타입 정보 (Memo 클래스 타입)와 @Id 의 타입을 지정하게 됩니다. 이처럼 Spring Data JPA 는 인터페이스 선언만으로도 자동으로 스프링의 빈(bean)으로 등록됩니다.(스프링이 내부적으로 인터페이스 타입에 맞는 객체를 생성해서 빈으로 등록합니다). 즉 JPA 가 자동으로 Repository 의 구현체도 만들고 컨테이너에 빈도 만들어 주어서 우리가 사용할 수 있게 되었다는 것을 의미합니다.  

 

MemoRepositoryTest 에서는 생성한 Entity 와 Repository 를 활용하여 DB에 작업을 수행할건데요. @SpringBootTest 어노테이션을 활용하여 Test 를 수행할 환경을 설정해줍니다. @Autowired 를 활용하여 MemoRepository 를 컨테이너에서 꺼내왔습니다. @Test 어노테이션을 활용하여 단위 테스트를 진행할 준비를 해주었고 Memo 객체를 Builder( ) 를 활용하여 생성한 다음 memoRepository의 save( ) 메서드로 생성한 Memo 객체를 DB에 저장하였습니다. 여기서는 save( ) 라는 JPA 의 기본 CRUD를 사용하였지만 내가 필요한 작업을 직접 만들어야 할때는 쿼리 메서드, @Query 애노테이션을 사용하여 작업할 수 있습니다. (추후에 배웁니다)

 

JpaRepository 인터페이스
위의 Memo 예제에서 MemoRepository 는 JpaRepository 인터페이스를 상속받는데요. JpaRepository 인터페이스를 상속받으면서 기본적으로 사용할 수 있는 메서드에는 다음과 같은 것들이 있습니다.
기본적인 CRUD는 JpaRepository 인터페이스를 상속받는 것만으로 해결가능합니다.