Post Entity 설계
DB에 데이터를 추가하기 위한 API를 설계한다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "post") // JPA에서 @Entity를 달아둔 class는 DB Table에 매핑됨
public class Post { // 게시글 Entity
// 외래키(FK)를 이용해 Table 간의 관계를 맺음.
// FK가 있는 Entity가 연관관계의 주인
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long postId;
private String title;
@Column(columnDefinition = "TEXT")
private String content;
// Many-to-One(다대일 관계) : 게시글 - 사용자
// 지연 로딩, 엔티티를 실제 사용할 때 조회
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id") // name 속성으로 DB에 저장될 이름 지정
private Member member;
@Builder
public Post(String title, String content, Member member) {
this.title = title;
this.content = content;
this.member = member;
}
}
@Table
: 객체와 테이블을 매핑하기 위한 어노테이션으로 JPA에서 @Entity가 붙은 클래스는 JPA가 관리한다. @Table은 이러한 Entity와 매핑할 테이블을 지정하고, 생략할 시 Entity의 이름을 테이블 이름으로 사용한다.@ManyToOne
: 연관관계를 매핑하기 위한 어노테이션으로, A To B에서 A가 해당 Entity를 의미한다.@Column
: 객체 필드와 컬럼을 매핑하기 위한 어노테이션. columnDefinition은 데이터베이스 컬럼 정보를 직접 줄 수 있다. nullable 속성은 default가 true이며, @Column 사용 시 nullable = false로 설정하는 것이 안전하다. 생략될 경우 알아서 @ManyToOne의 대상이 되는 Entity의 이름_id를 대상으로 삼는다.@JoinColumn
: 연관관계를 매핑하기 위한 어노테이션으로 외래 키를 매핑할 때 사용한다. name 속성을 통해 매핑할 외래 키 이름을 지정해 DB에 어떤 이름으로 저장될지 지정할 수 있다.@Builder
: 정보들은 자바빈즈패턴처럼 받되, 데이터 일관성을 위해 정보들을 다 받은 후에 객체를 생성하는 빌더 패턴에서 사용하는 어노테이션이다. 빌더 클래스를 직접 만들지 않아도 Lombok의 지원을 통해 클래스를 생성할 수 있다.(클래스 또는 생성자 위에 어노테이션 추가, 생성자 상단 선언 시 생성자에 포함된 필드만 빌더에 포함됨)Base Entity 설계 및 Member Entity 수정
Database의 각 Table에 공통적으로 넣는 Column들을 관리하기 편하게 하도록, 각각 Entity에 집어넣는 것이 아니라 BaseEntity를 추상 클래스로 만들어 활용한다.
// Database의 각 Table에 공통적으로 넣는 Column을 추상화하기 위한 클래스
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
@MappedSuperclass
: 매핑 정보가 해당 클래스에서 상속되는 Entity에 적용되는 클래스를 지정한다. 매핑된 슈퍼클래스에는 해당 클래스에 대해 정의된 별도의 테이블이 없다.@EntityListeners
: 인자로 callback listener 클래스를 넣어 Entity나 Mapped supereclass 전체에 적용되도록 하는 어노테이션이다. 여기서는 AuditingEntityListener.class를 사용했는데 이는 Spring Data JPA에서 Entity의 영속, 수정 이벤트를 감지하는 역할을 하는 이벤트 리스너 클래스이다.public class Member extends BaseTimeEntity {
...
}
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}
PostJpaRepository
Entity를 DB에 저장할 수 있는 save method는 JPA Repository에 존재하므로 따로 구현할 필요 없음.
public interface PostJpaRepository extends JpaRepository<Post, Long> {
}
PostCreateRequest DTO와 PostController 작성
게시글 작성 API. member의 id를 받아 게시글을 생성하는 방식으로 구현
public record PostCreateRequest(
String title,
String content
) {
}
@RestController
@RequestMapping("/api/posts")
@RequiredArgsConstructor
public class PostController {
// Request Header로 받아올 member id
private static final String CUSTOM_AUTH_ID = "X-Auth-ID";
private final PostService postService;
@PostMapping
public ResponseEntity<Void> createPost(@RequestHeader(CUSTOM_AUTH_ID) Long memberId, @RequestBody PostCreateRequest request) {
URI location = URI.create("/api/post/" + postService.create(request, memberId));
return ResponseEntity.created(location).build();
}
}
PostServicee
게시글 작성 API를 위한 비즈니스 로직 작성
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostService {
private final PostJpaRepository postJpaRepository;
private final MemberJpaRepository memberJpaRepository;
@Transactional
public String create(PostCreateRequest request, Long memberId) {
Member member = memberJpaRepository.findByIdOrThrow(memberId);
Post post = postJpaRepository.save(
Post.builder()
.member(member)
.title(request.title())
.content(request.content()).build()
);
return post.getPostId().toString();
}
}
참고자료
33기 DO SOPT 서버 파트 3차 세미나 자료(배포 불가)
[JPA] 엔티티와 매핑. @Entity, @Table, @Id, @Column..
@ManyToOne을 사용할 때 @JoinColumn 생략
Annotation Type MappedSuperclass
Annotation Type EntityListeners
Class AuditingEntityListener
Spring Data JPA Auditing으로 엔티티의 생성/수정 시각 자동으로 기록