테이블을 객체로 표현한 자바 클래스이다. 어노테이션을 사용해서 엔티티와 테이블을 매핑할 수 있다.
모든 필드는 접근이 가능해야 한다.
Lombok
을 사용해서 클래스 전체에 Getter
를 설정한다.
Setter
도 같이 설정하지 않는 이유는, 임의로 값을 입력하지 못하도록 하기 위함이다. 밑에 적겠지만, id
값같은 경우는 자동으로 값이 입력되도록 해서 중복값을 가지지 못하도록 할 것이다.
@Getter
public class Article { }
그냥 쉽게 출력하기 위해 사용한다.
@ToString
public class Article { }
순환 참조 에러를 해결할 수 있다.
Article
클래스 전체에 ToString
에너테이션이 있고, 그 안에는 ArticleComment
컬렉션이 있다.
그리고 ArticleComment
클래스에서 Article
을 참조하고 있다면 순환참조 에러가 발생한다.
Article
에서 ToString
-> ArticleComment
컬렉션 -> ArticleComment
에서 Article
-> 다시 Article
에서 ToString
-> ...
이런 문제를 해결하기 위해서 한 쪽의 ToString
을 끊어버리는 에너테이션을 붙이는 것이다.
@ToString
@Entity
public class Article {
@ToString.Exclude
@OrderBy("id")
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private final Set<ArticleComment> articleComments = new LinkedHashSet<>();
}
@ToString
@Entity
public class ArticleComment {
@Setter @ManyToOne(optional = false) private Article article;
}
Table
을 사용해서 인덱스를 잡을 수 있다.
@Table(indexes = {
@Index(columnList = "title"),
@Index(columnList = "hashtag"),
@Index(columnList = "createdAt"),
@Index(columnList = "createdBy")
})
public class Article { }
인덱스 사이즈에는 제한이 있어서 컬럼의 데이터타입이 너무 클 경우에는 인덱스를 걸 수 없다.
PK
값을 주기 위해 Id
어노테이션을 사용한다.
@Id
private Long id;
Auto Increment
(키값 자동증가)를 위해 GeneratedValue
어노테이션을 사용한다.
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
그 안에 strategy
라는 옵션을 줄 수 있다. MySQL
의 Auto Increment
는 IDENTITY
방식으로 만들어진다.
@CreatedDate private LocalDateTime createdAt; // 생성일시
@CreatedBy private String createdBy; // 생성자
@LastModifiedDate private LocalDateTime modifiedAt; // 수정일시
@LastModifiedBy private String modifiedBy; // 수정자
최초 등록할 때, 수정할 때 데이터를 자동으로 입력해준다.
이를 위해 JpaConfig
라는 클래스를 생성한 후 EnableJpaAuditing
어노테이션을 붙인다.
그리고 이 클래스 안에서 Auditing
할 때 필요한 설정을 세팅해준다.
@EnableJpaAuditing
@Configuration
public class JpaConfig {
@Bean
public AuditorAware<String> auditorAware(){
return () -> Optional.of("gamza"); // 스프링 시큐리티로 인증기능을 붙이게 될 때, 수정하자
}
}
람다식
으로 작성한 코드이고, 다른 방식으로도 작성 가능하다.
Optional
로 내보내주어야 하고, 임의의 이름을 하나 넣어주었다. 이렇게 되면 Auditing
할 때마다 이름은 gamza
로 입력된다.
후에 스프링 시큐리티를 사용해서 사용자 정보를 가져올 수 있게 되면 수정하면 된다.
그리고 Auditing
을 사용하면 Entity
에도 에너테이션을 통해 Auditing
을 사용한다는 것을 명시해주어야 한다.
이 코드를 적어주어야 제대로 동작한다.
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Article {
...
}
기본적으로 Column
어노테이션은 생략 가능하다. 그래서 따로 적히지 않았다면 컬럼으로 간주한다.
not null
속성을 걸고 싶다면 Column
어노테이션을 사용해서 적용할 수 있다.
기본값은 true(널값허용)
이기 때문에 널값을 허용하지 않겠다면 어노테이션을 적어주어야 한다.
@Column(nullable = false) private String content;
ERD 다이어그램을 만들 때 컬럼별로 데이터길이를 설정해주었을 것이다.
그때 정했던 길이를 length
를 사용해서 설정해줄 수 있다.
@Setter @Column(nullable = false, length = 10000) private String content;
해당 필드는 update
불가하다는 것을 명시해준다.
@Column(nullable = false, updatable = false) private LocalDateTime createdAt;
게시글 1개에 댓글 여러개가 있을 수 있는 것과 같은 관계일 때 사용한다.
이런 경우에는 댓글 엔티티에 작성한다.
optional
옵션이 false
값이다.@ManyToOne(optional = false) private Article article;
cascade
옵션을 주지 않는다.none
이다.게시글 1개에 댓글 여러개가 있을 수 있는 것과 같은 관계일 때 사용한다.
양방향 관계 설정. 이런 경우에는 게시글 엔티티에 작성한다.
List
, Map
, Set
을 사용해서 맵핑할 수 있다.
중복을 허용하지 않겠다는 의미로 Set
사용
mappedBy
로 이름을 지정하지 않으면 두 엔티티를 합친 것이 기본 이름이 된다.
@OrderBy("id")
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private final Set<ArticleComment> articleComments = new LinkedHashSet<>();
모든 JPA 엔티티들은 기본생성자
를 가지고 있어야 한다.
기본생성자는 평소에 오픈하지 않을 것이기 때문에 protected
로 하면 된다. public
으로 할 필요가 없다.
protected Article() {}
날짜와 관련된 컬럼에 파싱이 잘 되게 하기 위해서 사용할 수 있다.
iso
클래스를 사용하는 방법과 pattern
으로 문자열을 넣는 방법이 있다.
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@CreatedDate
@Column(nullable = false, updatable = false) private LocalDateTime createdAt
package com.fastcampus.projectboard.domain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
@Getter
@ToString
@Table(indexes = {
@Index(columnList = "title"),
@Index(columnList = "hashtag"),
@Index(columnList = "createdAt"),
@Index(columnList = "createdBy")
})
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter @Column(nullable = false) private String title; // 제목
@Setter @Column(nullable = false, length = 10000) private String content; // 본문
@Setter private String hashtag; // 해시태그
@ToString.Exclude
@OrderBy("id")
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private final Set<ArticleComment> articleComments = new LinkedHashSet<>();
@CreatedDate @Column(nullable = false) private LocalDateTime createdAt; // 생성일시
@CreatedBy @Column(nullable = false, length = 100) private String createdBy; // 생성자
@LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시
@LastModifiedBy @Column(nullable = false, length = 100) private String modifiedBy; // 수정자
protected Article() {}
private Article(String title, String content, String hashtag) {
this.title = title;
this.content = content;
this.hashtag = hashtag;
}
public static Article of(String title, String content, String hashtag){
return new Article(title, content, hashtag);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Article)) return false;
Article article = (Article) o;
return id != null && id.equals(article.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
package com.fastcampus.projectboard.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
@Getter
@ToString
@Table(indexes = {
@Index(columnList = "content"),
@Index(columnList = "createdAt"),
@Index(columnList = "createdBy")
})
@EntityListeners(AuditingEntityListener.class)
@Entity
public class ArticleComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter @ManyToOne(optional = false) private Article article; // 게시글 (ID)
@Setter @Column(nullable = false, length = 500) private String content; // 본문
@CreatedDate @Column(nullable = false) private LocalDateTime createdAt; // 생성일시
@CreatedBy @Column(nullable = false, length = 100) private String createdBy; // 생성자
@LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시
@LastModifiedBy @Column(nullable = false, length = 100) private String modifiedBy; // 수정자
protected ArticleComment() {}
private ArticleComment(Article article, String content) {
this.article = article;
this.content = content;
}
public static ArticleComment of(Article article, String content){
return new ArticleComment(article, content);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ArticleComment)) return false;
ArticleComment that = (ArticleComment) o;
return id != null && id.equals(that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
기본생성자 밑에 오버로딩 생성자로 주입하는 부분이랑 equals and hashCode 부분도 추가적으로 정리해야 하는데 아직 제대로 이해를 못해서, 추후에 추가로 정리하기로!