[SpringBoot/JPA] 게시글 작성 구현하기

[SpringBoot/JPA] 게시글 작성 구현하기

참조 블로그

https://velog.io/@max9106/Spring-Boot-JPA-MySQL-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%9E%91%EC%84%B1%EA%B8%B0%EB%8A%A5

[생성해야 할 클래스]

- Dto : controller와 service 사이에서 데이터를 주고 받는 역할(스프링에서 VO와 같은 것 같네여)

dto를 통해 service의 createPost에서 repository에 데이터를 집어넣는 순서라고 합니다,,,

- Service : 비즈니스 로직을 수행하는 역할

- Entity : DB 테이블과 매핑되는 객체

- Repository : 데이터 조작을 수행, 얘는 interface입니다.

스프링 프로젝트에서도 vo와 service 클래스가 있었는데 ORM, JPA를 사용하니까 entity와 repository가 생긴 것 같아요. 맞겠죠..? ORM 방식이 아니었다면 sql문으로 데이터베이스를 sql문으로 조작했을테니까,,,

블로그에서는 생성 순서를 entity->repository->service->dto 순으로 생성했는데

따라서 해보고나니 다음부터는 entity->dto->repository->service 순으로 하면 좋을 것 같다고 생각했어요

[클래스 생성]

- BoardEntity

package com.board.myspring.domain.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name="POST") public class BoardEntity extends TimeEntity{ @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Long id; @Column(length=10, nullable=false) private String writer; @Column(length=100,nullable=false) private String title; @Column(columnDefinition="TEXT",nullable=false) // mysql의 text 타입으로 지정하겠다는 의미 private String content; @Builder public BoardEntity(Long id, String title, String content, String writer) { this.id=id; this.title=title; this.content=content; this.writer=writer; } }

@Entity : 이 어노테이션이 붙은 클래스는 JPA가 관리합니다.

@Table : 매핑할 테이블을 지정

@NoArgsConstructor(access = AccessLevel.PROTECTED) : access = AccessLevel.PROTECTED 속성을 쓰지 않아도 되는데 쓰는 이유는 무분별한 객체 생성에 대해 한번 더 체크할 수 있는 수단이 된다고 합니다,,,

@Id : JPA가 객체를 관리할 때 식별할 기본키를 지정

@GeneratedValue(strategy = GenerationType.IDENTITY) : 기본키를 자동할당을 mysql에 위임하겠다. 기본키를 직접 지정하고자 하는 경우 @Id만 써야 합니다.

@Column(length=100,nullable=false) : varchar(100), notnull

@Column(columnDefinition="TEXT",nullable=false) : mysql에 text 타입이라는 게 있더라구요. varchar(255)보다 이점이 있으니까 사용하는 것 같습니다, notnull

@Builder : 객체를 생성하는 디자인 패턴 중에 빌더 패턴이라는 게 있다고 합니다.

https://mangkyu.tistory.com/163

- TimeEntity

package com.board.myspring.domain.entity; import java.time.LocalDateTime; import javax.persistence.Column; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import lombok.Getter; @Getter @MappedSuperclass // 테이블로 매핑하지 않고, 자식 entity에게 매핑 정보를 상속한다는 의미 @EntityListeners(AuditingEntityListener.class) // JPA에게 해당 entity는 auditing 기능을 사용한다는 것을 알림 public class TimeEntity { @CreatedDate @Column(updatable=false) private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime modifiedDate; }

@MappedSuperclass : 공통 매핑 정보가 필요할 때 사용하는 어노테이션이라고 합니다.

https://minkukjo.github.io/framework/2020/12/28/Spring-Data-JPA-15/

@EntityListeners(AuditingEntityListener.class) : EntityListeners에 Spring에서 제공하는 구현클래스인 AuditingEntityListener라는 Auditing 기능을 수행하는 리스너를 등록한 것

결국 @CreatedDate, @LastModifiedDate 어노테이션을 써서 자동으로 생성, 수정시간을 매핑하기 위한 어노테이션

이 어노테이션을 쓸 때는 main 클래스에 @EnableJpaAuditing 어노테이션을 붙여줘야 합니다.

- BoardApplication

package com.board.myspring; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing //jpa의 auditing 기능을 쓰기 위해서 필요한 어노테이션, timeentity에서 쓰임 @SpringBootApplication public class BoardApplication { public static void main(String[] args) { SpringApplication.run(BoardApplication.class, args); } }

@CreatedDate : 엔티티가 생성되어 저장될 때 시간이 자동 저장되도록 합니다.

@LastModifiedDate : 조회한 엔티티의 값을 변경할 때 시간이 자동 저장되도록 합니다.

- BoardDto

package com.board.myspring.dto; import java.time.LocalDateTime; import com.board.myspring.domain.entity.BoardEntity; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString @NoArgsConstructor public class BoardDto { private Long id; private String writer; private String title; private String content; private LocalDateTime createDate; private LocalDateTime modifiedDate; public BoardEntity toEntity() { BoardEntity boardEntity = BoardEntity.builder() .id(id) .writer(writer) .title(title) .content(content) .build(); return boardEntity; } @Builder public BoardDto(Long id, String writer, String title, String content, LocalDateTime createDate, LocalDateTime modifiedDate) { this.id = id; this.writer = writer; this.title = title; this.content = content; this.createDate = createDate; this.modifiedDate = modifiedDate; } }

@Getter, @Setter: lombok 어노테이션으로 getter, setter를 자동으로 생성해줍니다.

@ToString : lombok 어노테이션으로 tostring을 자동으로 생성해줍니다.

@NoArgsConstructor : lombok 어노테이션으로 파라미터 없는 기본 생성자를 자동으로 생성해줍니다.

- BoardRepository

package com.board.myspring.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.board.myspring.domain.entity.BoardEntity; public interface BoardRepository extends JpaRepository{ }

JpaRepository 인터페이스는 entity의 기본적인 CRUD가 가능하도록 해줍니다.

- BoardService

package com.board.myspring.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.board.myspring.domain.repository.BoardRepository; import com.board.myspring.dto.BoardDto; @Service public class BoardService { @Autowired private BoardRepository boardRepository; public Long createPost(BoardDto boardDto) { return boardRepository.save(boardDto.toEntity()).getId(); } }

BoardRepository를 주입할 때 참조 블로그와 다르게 @Autowired 어노테이션을 사용했습니다.

참조블로그는 메소드 위에 @Transactional 어노테이션을 썼는데 검색해보니 굳이 써야할 이유를 못 느껴서 뺏습니다.

나중에 트랜잭션에 대해 더 공부해보고 추가하는 게 좋을 것 같습니다!

15line : save 메소드가 JpaRepository가 제공하는 메소드입니다.

저는 crud를 생각해서 createPost라고 메소드명을 지었는데 savePost로 바꿔야하나 고민 중이에요...

[컨트롤러 코드]

- BoardController

package com.board.myspring.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import com.board.myspring.dto.BoardDto; import com.board.myspring.service.BoardService; @Controller public class BoardController { @Autowired private BoardService boardService; @GetMapping("/") public String list() { return "board/list.html"; } @GetMapping("/post") public String write() { return "board/write.html"; } @PostMapping("/post") public String write(BoardDto boardDto) { boardService.createPost(boardDto); return "redirect:/"; } }

여기도 @Autowired 어노테이션 변경했습니다.

[lombok 오류 원인/해결방법]

모든 클래스를 생성하고도 나타나는 오류들이 있었습니다.

the method builder() is undefined for the type BoardEntity

The method getId() is undefined for the type BoardEntity

- 원인

롬복 라이브러리는 getter/setter를 자동으로 생성해주는 라이브러리인데 gradle이나 maven에서 의존성만 추가해서는 IDE에서 바로 사용이 불가능하다고 합니다.

- 해결방법

1. dependency에 lombok을 추가해주고(프로젝트 생성할 때 이미 추가한 분들은 할 필요 없습니다.)

dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' }

2. 롬복 경로로 이동해주기

3. lombok.jar 파일이 있는 폴더에서 cmd 실행해주기

4. java -jar lombok-1.18.20.jar 명령어로 jar 실행해주기

5. IDE로 STS4 선택해주기

6. Install/Update -> Quit Instarller

7. STS 재시작, 하고도 오류라면 clean project 해주기

[게시글 한글 오류/해결방법]

게시글 작성 후 한글이 들어가면 오류가 뜹니다.

java.sql.SQLException: Incorrect string value: '\xE3\x85\x87\xE3\x85\x87' for column 'content' at row 1

- 시도1

생성됐던 테이블 삭제하고 database 뒤에 useUnicode와 characterEncoding 추가해보라고 해서 해봤지만 안 되더라구요.

spring.datasource.url=jdbc:mysql://localhost:3306/bootdb?useUnicode=true&characterEncoding;=UTF-8

- 시도2

ALTER TABLE `bootdb`.`post` COLLATE = utf8_bin ;

안 됩니다.

- 해결방법

위에 테이블의 캐릭터셋을 바꿔도 컬럼 각각은 latin으로 캐릭터셋이 돼있더라구요 따라서 컬럼도 각각 utf8로 바꿔주었습니다,,,,,

그냥 테이블은 따로 설정하면서 미리 만들어두는 게 빠를 것 같아요,,,,,

[여기까지 해 본 후기]

- 스프링보다 이것 저것 간단한 건 맞지만 파일 구조는 복잡한 듯하다.

- 테이블 생성 따로 안 해도 돼서 놀랐지만 한글 캐릭터셋 하는 게 시간 더 걸리는 듯함.

- sql 쿼리문 없이 save와 같은 메소드 하나로 되는 게 신기하다.

from http://juyeonee826.tistory.com/94 by ccl(A) rewrite - 2021-09-26 18:27:14