Spring : JPA 실습
요구사항 분석
- 게시판 기능
- 게시글 조회
- 게시글 등록
- 게시글 수정
- 게시글 삭제
- 회원 기능
- 구글 / 네이버 로그인
- 로그인한 사용자 글 작성 권한
- 본인 작성글에 대한 권한 관리
프로젝트에 Spring Data JPA 적용
Gradle
// SpringBoot Security Starter
implementation 'org.springframework.boot:spring-boot-starter-security'
// 웹 페이지
compile('org.springframework.boot:spring-boot-starter-web')
// lombok
compile('org.projectlombok:lombok')
// Jpa
compile('org.springframework.boot:spring-boot-starter-data-jpa')
// H2-Database
compile('com.h2database:h2')
// Session store
compile('org.springframework.session:spring-session-jdbc')
// Mustache
compile('org.springframework.boot:spring-boot-starter-mustache')
// Spring Security
compile('org.springframework.boot:spring-boot-starter-oauth2-client')
// Swagger
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
// Maria DB
compile('org.mariadb.jdbc:mariadb-java-client')
// Spring Security Test
testCompile('org.springframework.security:spring-security-test')
// Test
testCompile('org.springframework.boot:spring-boot-starter-test')
Domain
- 도메인을 담을 패키지
- 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역
기존 MyBatis: dao 와 조금 다르다. 그간 xml에 쿼리를 담고 클래스는 오로지 쿼리의 결과만 담던 일들이 모두 도메인 클래스에서 해결된다.
/**
* 어노테이션 순서 : 주요 어노테이션을 클래스와 가깝게
*
* @Getter 롬복은 간편하지만 필수는 아님 - 코틀린은 data class 로 롬복 필요 없음
* @NoArgsConstructor 파라미터가 없는 기본 생성자 생성 -> public Posts() {}
* @Entity 실제 DB 테이블과 매칭될 클래스, Entity 클래스라고도 부름름
*/
@Getter
@NoArgsConstructor
@Entity
public class Posts extends BaseTimeEntity {
/**
* @Id 해당 테이블의 PK
* @GeneratedValue 스프링 부트 2.0 에서는 IDENTITY 이 붙어야만 auto increment 실행
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* @Column 테이블 칼럼 선언, 없어도 되지만 length, columnDefinition 옵션을 조정하기 위해서 사용
*/
@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;
}
public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
- Entity 클래스에서는 절대 Setter 메소드를 만들지 않습니다.
- getter/setter를 무작정 생성하게 되면 해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확히 구분할 수 없어 차후 기능 변경 시 복잡해진다.
- 해당 필드의 값 변경이 필요하면 명확히 그 목적과 의도를 나타낼 수 있는 메소드를 추가해야만 한다.
Example
// 나쁜 예 public class Order{ public void setStatus(boolean status){ this.status = status } public void 주문서비스_취소이벤트(){ order.setStatus(false); } } // 올바른 예 public class Order{ public void cancelOrder(){ this.status = false } public void 주문서비스_취소이벤트(){ order.cancelOrder(); } }
- 그렇다면 Setter가 없는 이 상황에서 어떻게 값을 채워 DB에 insert 해야 할까요?
- 생성자 대신 @Builder을 통해 제공되는 빌더 클래스를 사용한다.
- 생성 시점에서 값을 채워주는 역할은 같지만 생성자의 경우 지금 채워야 할 필드가 무엇인지 명확이 지정할 수 없다.
- a,b의 위치를 변경해도 코드를 실행하기 전까지 문제를 찾을 수 없다. -> 시간 낭비, 스트레스 폭발 ....
public Example(String a, String b){ this.a = a; this.b = b; }
- 요로코롬 빌더 패턴으로 해주면 어느 필드에 어떤 값을 채워야할지 명확하게 인지할 수 있다.
Example.builder() .a(a) .b(b) .build();
PostsRepository
public interface PostsRepository extends JpaRepository<Posts, Long> { /** * 실제로 SpringDataJpa 에서 제공하는 기본 메소드로 해결 가능 * @Query 가 가독성이 좋긴 하다. * 규모가 있는 프로젝트에서는 Querydsl 을 사용하는게 좋다. * 이유 : * 1. 타입 안전성이 보장된다. * 2. 국내 많은 회사에서 사용 중이다.(쿠팡, 배민 등.. JPA 를 쓰는 회사들) * 3. 레퍼런스가 많다 -> 검색 자료가 많아서 좋음 */ @Query("SELECT p from Posts p ORDER BY p.id DESC ") List<Posts> findAllDesc(); }
- MyBatis 등에서 Dao라고 불리는 DB Layer 접근자이다.
- JPA에서는 Repository라고 부르며 interface로 생성한다.
- JpaRepository< Entity 클래스, PK 타입 > 를 상속하면 기본적인 CRUD 메소드가 자동으로 생성된다.
- Entity와 Repository는 함께 움직인다.
Spring Data JAP 테스트 코드 작성
@RunWith(SpringRunner.class) @SpringBootTest public class PostsRepositoryTest { ` @Autowired PostsRepository postsRepository; ` @After public void cleanup() { postsRepository.deleteAll(); } ` @Test public void 게시글저장_불러오기() { //given String title = "테스트 게시글"; String content = "테스트 본문"; ` postsRepository.save(Posts.builder() .title(title) .content(content) .author("[email protected]") .build()); ` //when List<Posts> postsList = postsRepository.findAll(); ` //then Posts posts = postsList.get(0); assertThat(posts.getTitle()).isEqualTo(title); assertThat(posts.getContent()).isEqualTo(content); } }
- @After
- Junit에서 단위 테스트가 끝날 때 마다 수행되는 메소드를 지정
- 보통 테스트 간 데이터 침범을 막기 위해 사용
- 테스트용 DB인 H2에 데이터가 남아있으면 안되므로 지워준다.
- 실제 DB 가 적용된 과정 로그
등록/수정/조회 API 만들기
- API를 만들기 위해 총 3개의 클래스가 필요하다.
- Request 데이터를 받을 Dto
- API 요청을 받을 Controller
- 트랜잭션, 도메인 기능 간의 순서를 보장하는 Service
Spring Web 계층
-
Web Layer
- 흔히 사용하는 컨트롤러(@Controller)와 JSP/Freemaker 등 뷰 템플릿 영역
- @Filter, 인터셉터, 컨트롤러 어드바이스 등(@ControllerAdvice)등 외부 요청과 응답에 대한 전반적인 영역 을 이야기한다.
-
Service Layer
- @Service에 사용되는 서비스 영역이다.
- 일반적으로 Controller 와 Dao 중간 영역에서 사용
- @Transactional이 사용되어야하는 영역
-
Repository
- Database와 같이 데이터 저장소에 접근하는 영역
- 기존 개발자는 Dao 영역으로 보면 이해가 쉽다.
-
Dtos
- 계층 간 데이터 교환을 위한 객체
- 뷰 템플릿 엔진에서 사용될 객체나 Repository Layer에서 결과로 넘겨준 객체 등이 이들을 이야기한다.
-
Domain Model
- 도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화시킨 것을 도메인 모델이라고 한다.
- 예를 들어 택시 앱이라고 하면 배차, 탑승, 요금 등 모두 도메인이 될 수 있다.
이 5가지 레이어에서 비즈니스 처리를 담당해야할 곳은 Domain 이다.
JPA Auditing으로 생성시간/수정시간 자동화하기
// BaseTimeEntity.java
/**
* 모든 Entity 의 상위 클래스가 된다.
* createdDate, modifiedDate 를 자동으로 관리하는 역할.
*
* @MappedSuperclass BaseTimeEntity 를 상속할 경우 필드(createdDate, modifiedDate)들도 칼럼으로 인식하도록 한다.
* @EntityListeners(AuditingEntityListener.class) BaseTimeEntity 클래스에 Auditing 기능을 포함시킨다.
* @CreatedDate Entity 가 생성되어 저장될 때 시간이 자동 저장된다.
* @LastModifiedDate 조회한 Entity 의 값을 변경할 때 시간이 자동 저장된다.
*/
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
}
// Posts.java
...
public class Posts extends BaseTimeEntity {
...
Test Code
@Test
public void BaseTimeEntity_등록() {
//given
LocalDateTime now = LocalDateTime.of(2019, 6, 4, 0, 0, 0);
postsRepository.save(Posts.builder()
.title("title")
.content("content")
.author("author")
.build());
//when
List<Posts> postsList = postsRepository.findAll();
//then
Posts posts = postsList.get(0);
System.out.println(">>>>>>>>> createDate=" + posts.getCreatedDate() + ", modifiedDate=" + posts.getModifiedDate());
assertThat(posts.getCreatedDate()).isAfter(now);
assertThat(posts.getModifiedDate()).isAfter(now);
}
Author And Source
이 문제에 관하여(Spring : JPA 실습), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@donsco/Spring-JPA-실습저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)