출처 : 이동욱 개발자님 [스프링 부트와 AWS로 혼자 구현하는 웹 서비스]
내가 보려고 쓰는 Spring 프로젝트 구조
들어가기 전...
내가 보려고 쓰는 프로젝트 구조
스프링 부트에서 JPA로 데이터베이스를 다뤄보자
왜 JPA를 쓸까?
왜 ❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓❓
국비학원에서 가르친 MyBatis...
이것은 곧 SQL 매퍼(mapper)이다.
하지만 이것은
실제로 개발하는 시간보다 SQL을 다루는 시간이 더 많았다.
분명 "객체지향 프로그래밍을 배웠는데, 왜 객체지향 프로그래밍을 못할까?"
→ 객체 모델링보다는 테이블 모델링에만 집중하고,
객체를 단순히 테이블에 맞추어 데이터 전달 역할만 했다.
→ 이것은 곧 기형적인 형태이다.
어떻게 하면 관계형 데이터베이스를 이용하는 프로젝트에서
객체지향 프로그래밍을 할 수 있을까?
문제의 해결책은
JPA라는 자바 표준 ORM(Object Relational Mapping) 기술에 있다.
ORM | SQL Mapper |
객체를 매핑 | SQL 쿼리를 매핑 |
즉, 개발자는 객체지향적으로 프로그래밍을 하고,
JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신 생성해서 실행한다.
개발자는 항상 객체지향적으로 코드를 표현할 수 있으니
더는 SQL에 종속적인 개발을 하지 않아도 된다.
domain 패키지
도메인을 담을 패키지
💡 도메인이란?
→ 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항
혹은 문제 영역이다.
❗ 기존에 MyBatis 같은 쿼리 매퍼를 사용했다면 dao 패키지를 떠올리겠지만,
dao 패키지와는 조금 결이 다르다.
→ 그간 xml에 쿼리를 담고, 클래스는 오로지 쿼리의 결과만 담던 일들이
모두 도메인 클래스라고 불리는 곳에서 해결된다.
[Posts.java]
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import practicejpa.practicejpa01.domain.BaseTimeEntity;
import javax.persistence.*;
@Getter // 6
@NoArgsConstructor // 5
@Entity // 1
public class Posts {
@Id // 2
@GeneratedValue(strategy = GenerationType.IDENTITY) // 3
private Long id;
@Column(length = 500, nullable = false) // 4
private String title;
@Column(columnDefinition = "Text", nullable = false)
private String content;
private String author;
@Builder // 7
public Posts(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
}
}
Posts 클래스는 실제 DB의 테이블과 매칭될 클래스이며,
보통 Entity 클래스라고 한다.
JPA를 사용하면, DB 데이터에 작업할 경우 실제 쿼리를 날리기보다는,
이 Entity 클래스의 수정을 통해 작업한다.
1. @Entity
- 테이블과 링크될 클래스임을 나타낸다.
- 기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭한다.
2. @Id
- 해당 테이블의 PK 필드를 나타낸다.
3. @GeneratedValue
- PK의 생성 규칙을 나타낸다.
- 스프링 부트 2.0에서는 GenerationType.IDENTITY 옵션을 추가해야만 auto_increment가 된다.
4. @Column
- 테이블의 칼럼을 나타내며 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 칼럼이 된다.
- 사용하는 이유는, 기본값 외에 추가로 변경이 필요한 옵션이 있으면 사용한다.
- 문자열의 경우 VARCHAR(255)가 기본값인데, 사이즈를 500으로 늘리고 싶거나, 타입을 TEXT로 변경하고 싶거나 등의 경우에 사용한다.
5. @NoArgsConstructor
- 기본 생성자 자동 추가
- public Posts() {}와 같은 효과
6. @Getter
- 클래스 내 모든 필드의 Getter 메소드를 자동 생성
7. @Builder
- 해당 클래스의 빌더 패턴 클래스를 생성
- 생성자 상단에 선언 시, 생성자에 포함된 필드만 빌더에 포함
❗ Setter 메소드가 없다.
Entity 클래스에는 절대 Setter 메소드를 만들지 않는다.
대신, 해당 필드의 값 변경이 필요하면
명확히 그 목적과 의도를 나타낼 수 있는 메소드를 추가해야 한다.
그럼 Setter가 없는 이 상황에서
어떻게 값을 채워 DB에 삽입해야 할까?
→ 기본적인 구조는,
생성자를 통해 최종값을 채운 후 DB에 삽입하는 것이다.
하지만 이 책에서는 생성자 대신에 @Builder를 통해 제공되는 빌더 클래스를 사용한다.
(생성자나 빌더나 생성 시점에 값을 채워주는 역할은 똑같다.
다만, 생성자의 경우 지금 채워야 할 필드가 무엇인지 명확히 지정할 수 없다.)
※ 빌더 패턴(Builder pattern)이란?
객체를 정의하고 그 객체를 생성할 때 보통 생성자를 통해 생성하는 것을 생각한다.
Bag bag = new Bag("name", 1000, "memo");
하지만 생성자를 통해 객체를 생성하는데 몇가지 단점이 있어 별도 builder를 두는 방법이 있다.
빌더 패턴은 빌더의 필드 이름으로 값을 설정하기 때문에 순서에 종속적이지 않다.
Bag bag = Bag.builder()
.name("name")
.money(1000)
.memo("memo")
.build();
→ 객체를 생성할 수 있는 빌더를 builder() 함수를 통해 얻고,
거기에 셋팅하고 마지막에 build()를 통해 빌더를 작동시켜 객체를 생성한다.
출처 : https://pamyferret.tistory.com/67
JpaRepository
Posts 클래스로 Database를 접근하게 해줄 JpaRepository를 생성한다.
[PostsRepository.java]
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostsRepository extends JpaRepository<Posts, Long> {
}
보통 MyBatis 등에서 Dao라고 불리는 DB Layer 접근자이다.
JPA에선 Repository라고 부르며 인터페이스로 생성한다.
단순히 인터페이스를 생성 후,
JpaRepository<Entity 클래스, PK 타입>를 상속하면
기본적인 CRUD 메소드가 자동으로 생성된다.
🚨 주의할 점
Entity 클래스와 기본 Entity Repository는 함께 위치해야 한다.
둘은 아주 밀접한 관계이고,
Entity 클래스는 기본 Repository 없이는 제대로 역할을 할 수가 없다.
Entity 클래스와 기본 Repository는 함께 움직여야 하므로
도메인 패키지에서 함께 관리한다.
Spring Data JPA 테스트 코드 작성하기
[PostRepository.test]
@SpringBootTest
class PostsRepositoryTest {
@Autowired
PostsRepository postsRepository;
@AfterEach // 1
public void cleanUp() {
postsRepository.deleteAll();
}
@Test
public void 게시글저장_불러오기() {
// given
String title = "테스트 게시글";
String content = "테스트 본문";
postsRepository.save(Posts.builder() // 2
.title(title)
.content(content)
.author("ummchicken")
.build());
// when
List<Posts> postsList = postsRepository.findAll(); // 3
// then
Posts posts = postsList.get(0);
assertThat(posts.getTitle()).isEqualTo(title);
assertThat(posts.getContent()).isEqualTo(content);
}
}
1. @AfterEach
- Junit에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정
- 여러 테스트가 동시에 수행되면, 테스트용 데이터베이스인 H2에 데이터가 그대로 남아 있어 다음 테스트 실행 시 테스트가 실패할 수 있다.
2. postsRepository.save()
- 테이블 posts에 insert / update 쿼리를 실행한다.
- id 값이 있다면 update, 없다면 insert 쿼리가 실행된다.
3. postRepository.findAll()
- 테이블 posts에 있는 모든 데이터를 조회해오는 메소드이다.
'Spring > 그 외' 카테고리의 다른 글
[Spring] 왜 스프링을 쓰는가? (특징 & 계층 구조) (0) | 2023.01.23 |
---|---|
Spring(스프링) 주요 어노테이션 정리 (0) | 2023.01.22 |
Dto, Controller, Service (0) | 2023.01.07 |
스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 (0) | 2023.01.04 |
혼자 구현하는 웹 서비스 (0) | 2023.01.03 |