dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.projectlombok:lombok')
annotationProcessor("org.projectlombok:lombok")
compile('org.springframework.boot:spring-boot-starter-data-jpa')//...1
compile('com.h2database:h2')//...2
testCompile('org.springframework.boot:spring-boot-starter-test')
}
domain 패키지는 도메인을 담을 패키지
여기서 도메인이란 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역
기존에 MyBatis와 같은 쿼리 매퍼를 사용했다면 dao 패키지를 떠올리겠지만 dao 패키지와는 조금 결이 다르다.
그간 xml에 쿼리를 담고, 클래스는 오로지 쿼리의 결과만 담던 일들이 모두 도메인 클래스라고 불리는 곳에서 해결된다.
Posts 클래스는 실제 DB의 테이블과 매칭될 클래스이며 보통 Entity 클래스라고도 한다.
JPA를 사용하면 DB 데이터에 작업할 경우 실제 쿼리를 날리기보다는, 이 Entity 클래스의 수정을 통해 작업한다.
Tip)
어노테이션 순서를 주요 어노테이션을 클래스에 가깝게 두자.
이렇게 어노테이션을 정렬하는 기준은 다음과 같다.
@Entity는 JPA의 어노테이션이며, @Getter와 @NoArgsConstructor는 롬복의 어노테이션
롬복은 코드를 단순화시켜 주지만 필수 어노테이션은 아니다.
그러다 보니 주요 어노테이션인 @Entity를 클래스에 가깝게 두고, 롬복 어노테이션을 그 위로 두었다.
이렇게 하면 이후에 코틀린 등의 새 언어 전환으로 롬복이 더 이상 필요 없을 경우 쉽게 삭제할 수 있다.
Posts 클래스의 코드
package com.jojoldu.book.springboot.domain.posts;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
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 클래스에는 JPA에서 제공하는 어노테이션들
@Entity
테이블과 링크될 클래스임을 나타냄
기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭
ex) SalesManager.java -> sales_manager (table)
JPA는 기본값으로 클래스 이름을 테이블 이름으로 사용하는데 아래의 Room 엔티티는 기본적으로 Room 테이블과 매핑됩니다.
@Entity public class Room { //... }
"기본적으로 테이블, 뷰, 칼럼을 비롯한 모든 식별자들은 소문자로 작성하는게 규칙인데 ORM에서 자동으로 테이블을 생성하면 소문자로 출력되는 것도 이런 규칙을 지키는 게 아닌가 싶다.
그래서 Posts가 posts로 생성되는듯 하다."
주의사항
@Table
엔티티와 매핑할 테이블을 지정
Table과 Entity의 쓰임 차이
@Entity(name ="")의 경우 말그대로 엔티티의 이름을 정할때 사용. 이는 HQL에서 엔티티를 식별할 이름을 정한다.
@Table(name ="")의 경우 Database에 생성될 table의 이름을 지정할때 사용.
@Table이 없고 @Entity(name ="")만 존재하는 경우, @Entity의 name 속성에 의해, Entity와 Table 이름이 모두 결정.
엔티티는 데이터베이스나 SQL상에 존재하지 않는다.
테이블과 달리 엔티티는 실제로 존재하지 않는 아닌 일종의 개념이다.
그러나 테이블은 데이터베이스나 SQL에 실제로 존재하며 물리적인 구조를 지니고 있다.
@Id
@GeneratedValue
PK의 생성 규칙을 나타냄
Hibernate 5부터 MySQL에서의 GenerationType.AUTO는 IDENTITY가 아닌
TABLE을 기본 시퀀스 전략으로 가져가기 때문에 스프링 2.0 에서는 GenerationType.IDENTITY 옵션을 추가해야 하는데,
이는 스프링 부트 2.0 버전과 1.5버전의 차이가 있다고 한다.
즉, 1.5에선 Hibernate 5를 쓰더라도 AUTO를 따라가지 않기 때문에 IDENTITY가 선택된다.
2.0에선 Hibernate 5를 그대로 따라가기 때문에 TABLE이 선택된다.
따라서, Hibernate의 id 생성 전략을 그대로 따라갈지 말지를 결정하는 Javadoc에서 제공하는 useNewIdGeneratorMappings 파일을 확인해보면 알 수 있다.
@GeneratedValue의 자세한 내용
주키 생성 전략(strategy) : IDENTITY전략의 자세한 내용
@Column
@NoArgsConstructor
@Getter
@Builder
❗ Entity 클레스에서는 절대 Setter 메소드를 만들지 않습니다.❗
단, 해당 필드의 값 변경이 필요하면 명확히 그 목적과 의도를 나타낼 수 있는 메소드를 추가해야만 한다.
이유는 Setter 메소드가 들어가게 되면 언제 어디서 변해야하는지 코드상으로 명확하게 구분할 수가 없어, 기능 변경시 복잡해지기 때문이다.
//잘못된 사용 예
public class Order {
public void setStatus (boolean status) {
this.status = status;
}
}
public void 주문서비스의_취소이벤드() {
order.setStatus(false);
}
//올바른 사용 예
public class Order {
public void cancelOreder() {
this.status = false;
}
}
public void 주문서비스의_취소이벤드() {
order.cancelOrder();
}
그러면, 어떻게 값을 채워 DB에 삽입하는 걸까 ??
기본적인 구조는 생성자를 통해 최종값을 채운 후 DB에 삽입하는 것이며,