gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.10'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'cho'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
tasks.named('test') {
useJUnitPlatform()
}
자바버전 11
스프링 2.7.10 입니다
JPA를 이용해 db를 컨트롤 할겁니다
DB 는 mysql 입니다.
JDBC 자바에서 db로 접속할수있게 도와주는 놈입니다.
rhymeleaf html단에서 꾸며주는 녀석입니다.
h2 테스트 코드 작성용으로 좋은녀석입니다.
lombok 서버포트 열때사용합니다.
application.properties
#DB settings
server.port=8080
spring.datasource.driver.class-name=org.mysqlDB.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=???
spring.datasource.url= jdbc:mysql://localhost:3306/board?serverTimezone=UTC
#JPA Settings
# create, create-drop, validate,update,none
spring.jpa.hibernate.ddl-auto= update
spring.jpa.generate-ddl=false
spring.jpa.show-sql=true
spring.jpa.database=mysql
#mysql jpa db
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
#H2 db jpa
#spring.jpa.database-platform= org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.format_sql=true
#Thymeleaf cache
spring.thymeleaf.cache = false
spring.thymeleaf.prefix= classpath:/templates/
spring.jpa.properties.hibernate.default_batch_fetch_size=1000
jpa 설정 더 자세한 설정 설명은
https://m.blog.naver.com/writer0713/221536526190
메인 페이지 index.html 부터 작성할 겁니다.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<button onclick="saveReq()">글작성</button>
</body>
<script>
// function saveReq() {
//
// }
const saveReq = () => {
location.href = "/board/save";
}
</script>
</html>
게시글 작성 버튼을 만들때
<button onclick="saveReq()">글작성</button>
</body>
<script>
// function saveReq() {
//
// }
const saveReq = () => {
location.href = "/board/save";
}
함수 하나를 지정하고 location.href = 글작성 경로;
를 작성합니다. 그리고 임시로 글작성 페이지를 만들어 놓습니다.
save.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>글작성</title>
</head>
<body>
</body>
</html>
이제 컨트롤러 단에 가서 게시물을 작성할 컨트롤러를 작성합니다.
BoardController
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index(){
return "index";
}
//글 작성 페이지
@GetMapping("/save")
public String writeForm(){
return "save";
}
- 모든 컨트롤러 클래스는 어노테이션@Controller 를 사용해야합니다.
- 컨트롤러 @RequiredArgsConstructor 생성자를 작성해야하는 번거러움을 줄여주는 어노테이션입니다. 프로젝트가 커질수록 생성자 코드가 많아지기때문에 불필요한 코드를 줄여줍니다.
- @RequestMapping 어노테이션은 mapping을 할때 생략할수있는 주소들을 생략 할수있게 해주는 어노테이션입니다현제 저의 주소는 board/000 인대 만약 저 어노테이션을 작성안하면 @GetMapping ("/board/save) 이렇게 일일이 다 적어줘야합니다 점점 Mapping 어노테이션이 많아지면 관리하기가 귀찮아 지기 때문에 추가해 주는것이 좋습니다.
실행하고 잘 동작하는지 확인해야합니다.
글작성 버튼을 누르면 자신이 지정한 경로에 게시글 작성 페이지가 떠야합니다.
게시글 작성 페이지를 좀더 추가합시다.
save.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>글작성</title>
</head>
<body>
<!-- front 부분은 되도록이면 복사 붙여넣기 해주세요 -->
<!-- action속성: 목적지(서버주소), method속성: http request method(get, post) -->
<form action="/board/save" method="post" enctype="multipart/form-data">
writer: <input type="text" name="boardWriter"> <br>
title: <input type="text" name="boardTitle"> <br>
contents: <textarea name="boardContents" cols="30" rows="10"></textarea> <br>
file: <input type="file" name="boardFile" multiple> <br>
<input type="submit" value="글작성">
</form>
</body>
</html>
- 반드시 개시글 작성자 이름이 있어야합니다.boardWriter 이부분이 있어야 서버쪽으로 넘어갈수 있습니다. 없으면 값겨서 갈수 있는 방법이 없습니다.
- textarea 는 글 작성한 내용을 좀 넓게 쓸려고 만든겁니다.
- 그리고 모든 입력한 데이터 값을 form 태그 안 url 주소로 보낸다는 뜻입니다.
게시글 작성한 값을 컨트롤러로 보내줘야합니다.보냈던 방식은 Post 방식이였습니다.
컨트롤러로 와서 코드 작성을 해줘야합니다. 그러나 작성하면서 받아오는 객체가 너무 많기 때문에 하나의 객체로
담아서 주고받기 위해 DTO를 사용하면 컨트롤러 단이거나 서비스 단에서 더 심플한 코드를 작성할 수 있습니다.
그래서 DTO를 작성하고 컨트롤 로 넘어가겠습니다.
BoardDTO
package cho.boardplus.dto;
import cho.boardplus.entity.BoardEntity;
import lombok.*;
import java.time.LocalDateTime;
//DTO(Data Transfer Object) 데이터를 전송할때 쓰는 객체 , VO ,Bean /Entity
@Getter
@Setter
@ToString//필드값 확인할때 사용
@NoArgsConstructor //기본 생성자
@AllArgsConstructor // 모든 필드를 매개변수로 하는 생성자.
public class BoardDTO {
// CRUD 파트
private Long id; //게시글 id
private String boardWriter; //글쓴이
private String boardTitle; // 게시글 이름
private String boardContents; //게시글 내용
private int boardHits; //조회수
private LocalDateTime boardCreatedTime; //게시글 작성 시간
private LocalDateTime boardUpdatedTime; // 게시글 수정시간
}
개시글 작성 페이지에서 받아오는 정보를 컨트롤러에 보낼때 편하게 하나로 뭉처서 보내기 편하게 만들었습니다.
DTO 를 작성하는 기준은 개시글 작성페이지 기준으로 작성을 했습니다.
그리고 컨트롤러 로 넘어가서는 간단하게 추가를 하면 됩니다.
BoardController
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index(){
return "index";
}
//글 작성 페이지
@GetMapping("/save")
public String writeForm(){
return "save";
}
// 글작성 컨트롤러
@PostMapping("save")
public String save(@ModelAttribute BoardDTO boardDTO){ //추가
System.out.println("boardDTO = "+boardDTO);
boardService.save(boardDTO);
return "index";
}
이렇게 세팅 하면 작성페이지의 네임과 DTO 의 네임 필드값이 동일 하다면 스프링이 해당하는 필드에 대한 Seter를
알아서 호출하면서 seter 메소드를 알아서 각각 담아줍니다. 그러고 나서 DTO 객체를 세팅을 해줘서 일일이
getter ,setter 일일이 코드 를 컨트롤러에서 작성해서 할필요 없이 간단하게 @ ModelAttribute 어노테이션으로 간단하게 할수 있습니다.
게시글을 작성했을때 잘 다듬어온 DTO 를 db안에 넣어줘야 합니다. DB 와 연결하기위해 Entity,Repository를 작성해야합니다.
먼저 Entity클래스를 먼저 작성합시다.
BoardEntity
package cho.boardplus.entity;
import cho.boardplus.dto.BoardDTO;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@Table(name= "board")
public class BoardEntity {
@Id //pk 컬럼 지정. 필수
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; //id 값
@Column(length = 20, nullable = false)//크기 20, not null
private String boardWriter;// 글 작성자
@Column
private String boardTitle; //제목
@Column(length = 500)
private String boardContents; //내용
@Column
private int boardHits;//조회수
}
이 엔티티 로 작성됀 그대로 JPA 가 db 테이블이 작성이 됩니다. 시간 관련한 Entity는 따로 관리를 합시다. 같이 관리를 해도 상관 없지만 위 해당 영상은 따로 관리하기때문에 저도 따로관리를 하겠습니다.
BaseEntity
package cho.boardplus.entity;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
//시간 정보를 다루는 엔티티
public class BaseEntity {
@CreationTimestamp //생성했을때 시간 체크
@Column(updatable = false) //수정시때는 발동하지 않는 옵션
private LocalDateTime createdTime;
@UpdateTimestamp //수정이 발생했을때 시간 체크
@Column(insertable = false) //인설트를 할때는 관여를 안한다.
private LocalDateTime updatedTime;
}
이제 기존 BoardEntity 에 상속을 받으면 됩니다. 이제 회원가입할때나, 다른 db 에 시간이 필요할때 상속을해서 활용할수 있습니다.
BoardEntity
package cho.boardplus.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@Table(name= "board")
public class BoardEntity extends BaseEntity{//< 상속 추가
@Id //pk 컬럼 지정. 필수
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; //id 값
@Column(length = 20, nullable = false)//크기 20, not null
private String bardWriter;// 글 작성자
@Column
private String boardTitle; //제목
@Column(length = 500)
private String boardContents; //내용
@Column
private int boardHits;//조회수
}
이제 서비스 와 레포지토리를 작성해야합니다.
BoardRepository
package cho.boardplus.boardrepository;
import cho.boardplus.entity.BoardEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BoardRepository extends JpaRepository<BoardEntity,Long> {
}
인터페이스 형태로 레포지토리를 만드시고 상속으로 JpaRepository 를 받으시고 제네릭<BoardEntity,Long> 타입은 Long 타입으로 엔티티를 받을수 있게 작성해줍니다.
이제 서비스 클래스를 작성해야합니다. 그전에 컨트롤러에서 미리 작성을 해줍시다.
BoardControllr
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor //추가
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService; //추가
//메인페이지
@GetMapping("/index")
public String index() {
return "index";
}
//글 작성 페이지
@GetMapping("/save")
public String writeForm() {
return "save";
}
// 글작성 컨트롤러
@PostMapping("save")
public String save(@ModelAttribute BoardDTO boardDTO) {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return "index";
}
}
private final BoardService boardservice; 는 생성자 주입 방식으로 의존성을 주입 받게 됩니다. 컨트롤러에 이제 서비스 클래스를 추가하고 boardservice 로 가서 클래스를 마저 작성해야합니다.
// 글작성 컨트롤러
@PostMapping("save")
public String save(@ModelAttribute BoardDTO boardDTO) {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return null;
}
서비스로 갑시다.
주입을 받아야하기때문에 컨트롤에서 똑같은 방식으로 받았던거처럼 레포지토리도 서비스에서 추가합니다.
주로 서비스에서 객체를 받아오는것들은 대부분 엔티티 객체 입니다. 그래서 DTO->Entity 로 변형을 하거나
Entity ->DTO 로 변환해야합니다. 정확히는 서비스에서 컨트롤러로 넘길때는 DTO 로 변환해서 넘기고
레포지토리는 엔티티만 받을수 있기 때문에 넘길때는 Entity로 변환해서 넘겨야 합니다.
엔티티는 DB와 직결적으로 연결이 되어 있기때문에 최대한 노출을 시키면 안된다는 이야기들을 많이 합니다.
그래서 엔티티 클래스는 최대한 서비스 클래스까지만 오도록 코드를 작성을 해야합니다. 그래서 DTO로 변환하고
DTO -> Entity 로 변환 한다고 생각 하시면 됩니다.
BoardService
package cho.boardplus.service;
import cho.boardplus.boardrepository.BoardRepository;
import cho.boardplus.dto.BoardDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
//DTO -> Entity (Entity Class)
//Entity -> DTO (DTO Class)
@Service
@RequiredArgsConstructor
public class BoardService {
private BoardRepository boardRepository;
public void save(BoardDTO boardDTO) {
boardRepository.save();
}
}
.save는 아직 엔티티에서 DTO로 변환을 안했기 때문에 오류가 걸릴겁니다. 이제 엔티티 로 가서 Entity ->DTO 로 변환하는 작업을 실시합시다. 다양한 방법이 있습니다 빌더 형태로 변환 하는 방법도 있고 또는 서비스 클래스 안에서 변환하는 방법도 있습니다.
BoardEntity
package cho.boardplus.entity;
import cho.boardplus.dto.BoardDTO;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@Table(name= "board")
public class BoardEntity extends BaseEntity {
@Id //pk 컬럼 지정. 필수
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; //id 값
@Column(length = 20, nullable = false)//크기 20, not null
private String boardWriter;// 글 작성자
@Column
private String boardTitle; //제목
@Column(length = 500)
private String boardContents; //내용
@Column
private int boardHits;//조회수
//entity 를 DTO 로 변환하는 작업
public static BoardEntity toSaveEntity(BoardDTO boardDTO) {
BoardEntity boardEntity = new BoardEntity();
boardEntity.setBoardWriter(boardDTO.getBoardWriter());
boardEntity.setBoardTitle(boardDTO.getBoardTitle());
boardEntity.setBoardContents(boardDTO.getBoardContents());
boardEntity.setBoardHits(0);
return boardEntity;
}
}
BoardService
package cho.boardplus.service;
import cho.boardplus.repository.BoardRepository;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.entity.BoardEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
//DTO -> Entity (Entity Class)
//Entity -> DTO (DTO Class)
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
//작성
public void save(BoardDTO boardDTO) {
BoardEntity boardEntity = BoardEntity.toSaveEntity(boardDTO); //추가
boardRepository.save(boardEntity); //추가
}
}
엔티티 객체를 받아올수 있게 toSaveEntity 클래스를 작성해서 추가 했습니다. 그리고
boardRepository.save() 안에 boardEntity를 넘겨주면 insert 쿼리가 나가게 됩니다.
컨트롤러에서 리턴값을 null 로 잠시 지정했던걸 다시 수정해줍니다.
BoardController
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index() {
return "index";
}
//글 작성 페이지
@GetMapping("/save")
public String writeForm() {
return "save";
}
// 글작성 컨트롤러
@PostMapping("save")
public String save(@ModelAttribute BoardDTO boardDTO) {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return "index"; //index 로 변경
}
}
글 작성시 db로 잘 넘어오는지 확인을 해야합니다.
컨트롤러에서 글을 작성 완료하면 리턴값을 index로 설정했던거 기억하실겁니다.
return "index"; // <index 로 변경
db에 잘 들어왔는걸 볼수 있습니다.
'Spring > 게시판 CRUD + 추가기능들' 카테고리의 다른 글
게시글 수정 (0) | 2023.03.29 |
---|---|
게시글 상세조회 (0) | 2023.03.28 |
게시글 목록 (0) | 2023.03.28 |
게시판 댓글 기능 추가 (ajax) (0) | 2023.02.17 |
mysql db id 연결 (0) | 2023.02.14 |