헤르메스 LIFE

[Spring Boot] 게시판 #2 - 간단한 게시판 CRUD 개발 본문

Spring Boot Framework

[Spring Boot] 게시판 #2 - 간단한 게시판 CRUD 개발

헤르메스의날개 2023. 4. 23. 01:55
728x90

octopus_bbs.zip
0.41MB

게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다.

https://hermeslog.tistory.com/704

 

[Spring Boot] 게시판 #1 - 개발환경

게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다. 개발환경 STS 4.17.2.RELEASE OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS) Spring Boot 2.7.9 lombok devtools postgresql 14.1 Gladle Thymeleaf

hermeslog.tistory.com


개발환경

STS 4.17.2.RELEASE

OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS)

Spring Boot 2.7.9

lombok

devtools

postgresql 14.1

Gladle

Thymeleaf


T_BOARD_M 테이블

DROP TABLE IF EXISTS T_BOARD_M;

CREATE TABLE IF NOT EXISTS T_BOARD_M (
    ID SERIAL NOT NULL,
    TITLE VARCHAR(200) NOT NULL,
    CONTENTS TEXT NOT NULL,
    READ_CNT INTEGER NOT NULL DEFAULT 0 ,
    CRT_ID VARCHAR(20),
    CRT_DT TIMESTAMP,
    MDF_ID VARCHAR(20),
    MDF_DT TIMESTAMP,
    CONSTRAINT PK_T_BOARD_M PRIMARY KEY (ID)
);

BoardController.java

package octopus.bbs.controller;

import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import octopus.base.anotation.LoginUser;
import octopus.base.model.CommonResult;
import octopus.base.model.ListResult;
import octopus.base.model.SingleResult;
import octopus.base.model.UserSessionDto;
import octopus.base.service.ResponseService;
import octopus.bbs.dto.BoardDto;
import octopus.bbs.service.BoardService;

@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/bbs")
public class BoardController {
    
    private final MessageSourceAccessor messageSourceAccessor;
    private final BoardService          boardService;
    private final ResponseService       responseService;
    
    @GetMapping("/id/{id}")
    public SingleResult<BoardDto> findCodeByCd(@PathVariable Long id) {
        return responseService.getSingleResult(boardService.findById(id));
    }
    
    @GetMapping("/list")
    public ListResult<BoardDto> findAll() {
        return responseService.getListResult(boardService.findAll());
    }
    
    @PostMapping("/save")
    public SingleResult<BoardDto> save(final @RequestBody BoardDto dto,
            @LoginUser UserSessionDto userDto) {
        
        dto.setCrtId(userDto.getUserId());
        dto.setMdfId(userDto.getUserId());
        
        log.debug("BoardDto :: {}", dto);
        
        return responseService.getSingleResult(boardService.save(dto));
    }
    
    @PutMapping("/update")
    public SingleResult<String> update(final @RequestBody BoardDto dto,
            @LoginUser UserSessionDto userDto) {
        
        dto.setMdfId(userDto.getUserId());
        
        log.debug("dto :: {}", dto);
        
        boardService.update(dto);
        
        return responseService.getSingleResult(messageSourceAccessor.getMessage("msg.itIsSaved")); // 저장되었습니다.
    }
    
    @DeleteMapping("/id/{id}")
    public CommonResult delete(@PathVariable Long id) {
        
        boardService.delete(id);
        
        return responseService.getSingleResult(messageSourceAccessor.getMessage("msg.itIsDeleted")); // 삭제되었습니다.
    }
}

BoardService.java

package octopus.bbs.service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import octopus.bbs.dto.BoardDto;
import octopus.bbs.dto.TBoardM;
import octopus.bbs.repository.BoardRepository;

@Service
// @AllArgsConstructor
@RequiredArgsConstructor
@Slf4j
public class BoardService {
    // @AllArgsConstructor를 사용하는 경우
    // private CodeDRepository codeDRepository;
    
    private final BoardRepository boardRepository;
    private final ModelMapper     modelMapper;
    
    @Transactional // 선언적 트랜잭션을 사용
    public BoardDto findById(Long id) {
        Optional<TBoardM> board = boardRepository.findById(id);
        
        log.debug("board :: {}", board.get());
        
        if (board.isPresent()) {
            boardRepository.updateCnt(id);
            return modelMapper.map(board.get(), BoardDto.class);
        } else {
            return new BoardDto();
        }
    }
    
    @Transactional(readOnly = true) // 선언적 트랜잭션을 사용
    public List<BoardDto> findAll() {
        List<BoardDto> list = boardRepository.findAll().stream()
                .map(data -> new BoardDto(data))
                .collect(Collectors.toList());
        
        log.debug("list :: {}", list);
        
        return list;
    }
    
    @Transactional
    public BoardDto save(BoardDto dto) {
        
        log.debug("BoardDto :: {}", dto);
        
        TBoardM board = dto.toEntity();
        
        log.debug("tCodeM :: {}", board);
        
        TBoardM saveBoard = boardRepository.save(board);
        
        return new BoardDto(saveBoard);
    }
    
    @Transactional
    public void update(BoardDto dto) {

        log.debug("BoardDto :: {}", dto);
        
        Optional<TBoardM> board = boardRepository.findById(dto.getId());
        
        log.debug("board :: {}", board.get());
        
        board.get().updateBoard(dto);
    }
    
    @Transactional
    public void delete(Long id) {
        log.debug("삭제될 ID :: {}", id);
        
        boardRepository.deleteById(id);
    }
}

BoardRepository.java

package octopus.bbs.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import octopus.bbs.dto.TBoardM;

// @Repository : JpaRepository를 사용하면 @Repository를 사용하지 않아도 됨.
public interface BoardRepository extends JpaRepository<TBoardM, Long> {
    String UPDATE_CNT = "UPDATE TBoardM a" +
            "   SET a.readCnt = a.readCnt + 1 " +
            " WHERE a.id = ?1";
    
    @Modifying(clearAutomatically = true, flushAutomatically = true)
    // @Query(value=UPDATE_CNT, nativeQuery=true)
    @Query(value = UPDATE_CNT)
    int updateCnt(Long id);
}

TBoardM.java

package octopus.bbs.dto;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Proxy;
import org.springframework.util.Assert;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import octopus.base.model.BaseEntity;

@Getter // getter를 자동으로 생성합니다.
// @Setter // 객체가 무분별하게 변경될 가능성 있음
// @ToString(exclude = { "crtId", "crtDt", "mdfId", "mdfDt" }) // 연관관계 매핑된 엔티티 필드는 제거. 연관 관계 필드는 toString()에서 사용하지 않는 것이
// // 좋습니다.
@ToString(callSuper = true)
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 인자없는 생성자를 자동으로 생성합니다. 기본 생성자의 접근 제어자가 불명확함. (access =
                                                   // AccessLevel.PROTECTED) 추가
@DynamicInsert // insert 시 null 인필드 제외
@DynamicUpdate // update 시 null 인필드 제외
// @AllArgsConstructor // 객체 내부의 인스턴스멤버들을 모두 가지고 있는 생성자를 생성 (매우 위험)
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) // Post Entity에서 User와의 관계를 Json으로 변환시 오류 방지를 위한 코드
@Proxy(lazy = false)
@Entity // jpa entity임을 선언. 실제 DB의 테이블과 매칭될 Class
@Table(name = "T_BOARD_M")
public class TBoardM extends BaseEntity {
    private static final long serialVersionUID = 1L;
    
    /**
     * ID
     */
    @Id // PK 필드임
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * 제목
     */
    // @Column(nullable = false, length = 200)
    private String title;
    
    /**
     * 내용
     */
    private String contents;
    
    /**
     * 조회수
     */
    private Integer readCnt;

    @Builder
    public TBoardM(Long id, String title, String contents, Integer readCnt, String crtId,
            String mdfId) {
        Assert.hasText(title, "Title must not be empty");
        Assert.hasText(crtId, "crtId must not be empty");
        Assert.hasText(mdfId, "mdfId must not be empty");
        
        this.id       = id;
        this.title    = title;
        this.contents = contents;
        this.readCnt  = readCnt;
        super.crtId   = crtId;
        super.mdfId   = mdfId;
    }
    
    /**
     * 게시판 Update
     */
    public void updateBoard(BoardDto dto) {
        this.title    = dto.getTitle();
        this.contents = dto.getContents();
        super.mdfId   = dto.getMdfId();
    }
    
}

BoardDto.java

package octopus.bbs.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import octopus.base.model.BaseDto;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@Builder
@EqualsAndHashCode(callSuper = true) // true의 경우 부모 클래스 필드 값들도 동일한지 체크하며, false(기본값)일 경우 자신 클래스의 필드 값만 고려한다.
public class BoardDto extends BaseDto {
    private static final long serialVersionUID = 1L;
    
    private Long    id;
    private String  title;
    private String  contents;
    private Integer readCnt;
    
    public TBoardM toEntity() {
        return TBoardM.builder().title(title).contents(contents)
                .crtId(getCrtId()).mdfId(getMdfId()).build();
    }
    
    public BoardDto(TBoardM board) {
        this.id       = board.getId();
        this.title    = board.getTitle();
        this.contents = board.getContents();
        this.readCnt  = board.getReadCnt();
        super.crtId   = board.getCrtId();
        super.crtDt   = board.getCrtDt();
        super.mdfId   = board.getMdfId();
        super.mdfDt   = board.getMdfDt();
    }
}

Spring Boot Start 옵션 : Run Configuration >> Spring Boot App >> Arguments Tab > VM arguments > -Dspring.profiles.active=local

테스트

 


https://hermeslog.tistory.com/704

 

[Spring Boot] 게시판 #1 - 개발환경

게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다. 개발환경 STS 4.17.2.RELEASE OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS) Spring Boot 2.7.9 lombok devtools postgresql 14.1 Gladle Thymeleaf

hermeslog.tistory.com

 

728x90