[Java+SpringBoot+JPA] To do List 게시판 만들기 (시작일/마감일 설정)

2024. 1. 9. 15:08Pratice/CRUD

반응형

 

 

 


 

🟢 Entity

@Entity
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "todolist")
@SequenceGenerator(
    name = "todolist_SEQ",
    sequenceName = "todolist_SEQ",
    allocationSize = 1,
    initialValue = 1)
public class TodoEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "todolist_SEQ")
  @Column(name="num")
  private Integer num;

  @Column(name="title", length = 100, nullable = false)
  private String title;

  @Lob
  @Column(name="content", length = 500, nullable = false)
  private String content;

  @Column(name="importance", length = 20)
  private String importance;

  @Column(name="startDay")
  private String startDay;

  @Column(name="deadline")
  private String deadline;

}

 

 

🟢 DTO


@Data
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TodoDTO {

  private Integer  num;

  @NotEmpty (message = "제목은 필수 입력입니다.")
  @Size(max=100, message="100자를 넘을 수 없습니다.")
  private String title;

  @NotEmpty (message = "내용은 필수 입력입니다.")
  @Size(max=300, message="300자를 넘을 수 없습니다.")
  private String content;

  private String importance;

  private String startDay;  //시작일

  private String deadline;   //마감일

}

 

 

 

🟢 Repository

▪️ 검색 조건 : 제목, 내용, 제목+내용, 중요도

@Repository
public interface TodoRepository extends JpaRepository<TodoEntity, Integer> {

  //검색조건
  //제목
  @Query("SELECT t FROM TodoEntity t WHERE t.title LIKE %:keyword%")
  Page<TodoEntity> findByTitle (@Param("keyword") String keyword, Pageable pageable);

  //내용
  @Query("SELECT t FROM TodoEntity t WHERE t.content LIKE %:keyword%")
  Page<TodoEntity> findByContent (@Param("keyword") String keyword, Pageable pageable);

  //제목+내용
  @Query("SELECT t FROM TodoEntity t WHERE t.title LIKE %:keyword% OR t.content LIKE %:keyword%")
  Page<TodoEntity> findByTitleOrContent (@Param("keyword") String keyword, Pageable pageable);

  //중요도
  @Query("SELECT t FROM TodoEntity t WHERE t.importance LIKE %:keyword%")
  Page<TodoEntity> findByImportance (@Param("keyword") String keyword, Pageable pageable);

}

 

더보기

▪️ Repository Test

@SpringBootTest
public class TodoRepositoryTest {

  @Autowired
  private TodoRepository todoRepository;

  //삽입
  @Test
  public void insert() throws Exception {
    for (int i = 1; i < 20; i++) {
      TodoEntity todoEntity = TodoEntity.builder()
          .title("할 일" + i)
          .content("해야할 일" + i)
          .importance("중요")
          .build();
      todoRepository.save(todoEntity);
    }
  }

}

 

 

🟢 Service

@Service
@Transactional
@RequiredArgsConstructor
public class TodoService {

  private final TodoRepository todoRepository;
  private final ModelMapper modelMapper = new ModelMapper();


  //삽입 : 글 등록
  public TodoEntity insert(TodoDTO todoDTO) throws Exception {

    TodoEntity todoEntity = modelMapper.map(todoDTO, TodoEntity.class);
    todoRepository.save(todoEntity);

    return todoEntity;

  }


  //전체 조회 (검색, 페이징처리)
  public Page<TodoDTO> findAll(String type, String keyword, Pageable page) throws Exception {

    //페이지 조건 설정
    int currentPage = page.getPageNumber()-1;
    int blockLimit = 5;

    Pageable pageable = PageRequest.of(currentPage, blockLimit, Sort.by(Sort.Direction.DESC, "num"));


    //검색조건
    Page<TodoEntity> todoEntityPage;

    if(("t").equals(type) && keyword != null) {
      todoEntityPage = todoRepository.findByTitle(keyword, pageable);
    } else if(("c").equals(type) && keyword != null) {
      todoEntityPage = todoRepository.findByContent(keyword, pageable);
    } else if(("tc").equals(type) && keyword != null) {
      todoEntityPage = todoRepository.findByContent(keyword, pageable);
    } else {
      todoEntityPage = todoRepository.findAll(pageable);
    }

    List<TodoDTO> todoDTOPage = todoEntityPage.stream()
        .map(todoEntity -> modelMapper.map(todoEntity, TodoDTO.class))
        .collect(Collectors.toList());

    return new PageImpl<>(todoDTOPage, pageable, todoEntityPage.getTotalElements());

  }


  //개별조회
  public TodoDTO findOne (Integer num) throws Exception {

    Optional<TodoEntity> todoEntity = todoRepository.findById(num);
    TodoDTO todoDTO = modelMapper.map(todoEntity, TodoDTO.class);

    return todoDTO;

  }


  //수정조회
  public void update(TodoDTO todoDTO) throws Exception {

    Integer num = todoDTO.getNum();
    Optional<TodoEntity> todoEntity = todoRepository.findById(num);

    if(todoEntity.isPresent()) {
      TodoEntity update = todoEntity.get();
      modelMapper.map(todoDTO, update);

      todoRepository.save(update);
    }

  }


  //삭제
  public void delete(Integer num)throws Exception {
    todoRepository.deleteById(num);
  }


}

 

 

 

 

🟢 Controller

@Controller
@RequiredArgsConstructor
public class TodoController {

  private final TodoService todoService;

  //목록 + 검색 + 조회
  @GetMapping("/todolist")
  public String todoListForm(@RequestParam(value = "type", defaultValue = "") String type,
                             @RequestParam(value = "keyword", defaultValue = "") String keyword,
                             @PageableDefault(page=1) Pageable pageable,
                             Model model) throws Exception {

    Page<TodoDTO> todoDTOPage = todoService.findAll(type, keyword, pageable);

    int blockLimit = 3;
    int startPage = (((int)(Math.ceil((double)pageable.getPageNumber()/blockLimit)))-1) * blockLimit+1;
    int endPage = Math.min(startPage+blockLimit-1, todoDTOPage.getTotalPages());

    int prevPage = todoDTOPage.getNumber();
    int currentPage = todoDTOPage.getNumber()+1;
    int nextPage = todoDTOPage.getNumber()+2;
    int lastPage = todoDTOPage.getTotalPages();

    todoDTOPage.getTotalElements();   //전체 게시글 수

    model.addAttribute("lists", todoDTOPage);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("prevPage", prevPage);
    model.addAttribute("currentPage", currentPage);
    model.addAttribute("nextPage", nextPage);
    model.addAttribute("lastPage", lastPage);

    model.addAttribute("type", type);
    model.addAttribute("keyword", keyword);

    return "/todo/list";
  }


  //삽입 폼
  @GetMapping("/todoinsert")
  public String todoInsertForm(Model model) throws Exception {
    TodoDTO todoDTO = new TodoDTO();
    model.addAttribute("todoDTO", todoDTO);
    System.out.println("insertForm : " + todoDTO);
    return "/todo/insert";
  }
  //삽입 처리
  @PostMapping("/todoinsert")
  public String todoInsertProc(@Valid TodoDTO todoDTO, BindingResult bindingResult) throws Exception {

    if(bindingResult.hasErrors()) {
      System.out.println("insertProc1 : " + todoDTO);
      return "/todo/insert";
    } todoService.insert(todoDTO);

    System.out.println("insertProc2 : " + todoDTO);

    return "redirect:/todolist";
  }


  //상세 폼
  @GetMapping("/tododetail")
  public String todoDetailForm(Integer num, Model model) throws Exception {
    TodoDTO todoDTO = todoService.findOne(num);
    model.addAttribute("todoDTO", todoDTO);
    return "/todo/detail";
  }


  //수정 폼
  @GetMapping("/todoupdate")
  public String todoUpdateForm(Integer num, Model model) throws Exception {
    TodoDTO todoDTO = todoService.findOne(num);
    model.addAttribute("todoDTO", todoDTO);
    return "/todo/update";
  }
  //수정 폼
  @PostMapping("/todoupdate")
  public String todoUpdateProc(@Valid TodoDTO todoDTO, BindingResult bindingResult) throws Exception {

    if(bindingResult.hasErrors()) {
      return "/todo/update";
    } todoService.update(todoDTO);

    return "redirect:/todolist";
  }


  //삭제 폼
  @GetMapping("/tododelete")
  public String todoDeleteForm(int num) throws Exception {
    todoService.delete(num);
    return "redirect:/todolist";
  }

}

 

 


 

🟢 list.html

 

▪️ 게시물이 0개일 때 아무것도 표시되지 않도록

<div class="d-flex justify-content-center" th:if="${lastPage > 1}">
<body>
<!-- content 영역 -->
<div layout:fragment="content">
  <div class="row mt-5">
    <div class="col-sm-3"></div>    <!-- 좌측 여백 -->
    <div class="col-sm-6">        <!-- 본문 시작-->
      <h2><span class="pagination justify-content-center">TO DO LIST</span></h2>

      <div class="d-grid gap-2 d-md-flex justify-content-md-end">
        <b>* 총 게시글 수 : <span th:text="${lists.getTotalElements}"></span> 건</b>
      </div>

      <!-- 반복 영역 -->
      <div th:each="data:${lists}">
        <div class="card mt-3">  <!-- card 영역 -->
          <h5 class="card-header" style="cursor: pointer" th:onclick="|location.href='@{/tododetail(num=${data.num})}'|">  <!-- card header 영역 -->
            <div class="row">
              <div class="col-sm-6">
                <div class="form-check">
                  <input class="form-check-input" type="checkbox" value="" id="title">
                  <label class="form-check-label" name="title" th:text="${data.title}">할 일</label>
                </div>
              </div>
              <div class="col-sm-2" th:text="${data.startDay}">등록일</div>
              <div class="col-sm-2" th:text="${data.deadline}">등록일</div>
              <div class="col-sm-2" name="importance" th:value="${importance}">    <!-- 난이도 -->
                <span class="badge text-bg-danger" th:if="${data.importance=='1'}">Hight</span>
                <span class="badge text-bg-warning" th:if="${data.importance=='2'}">Medium</span>
                <span class="badge text-bg-info" th:if="${data.importance=='3'}">Low</span>
              </div>
            </div>
          </h5>  <!-- card header 영역 끝 -->

          <div class="card-body">
            <p class="card-text" name="content" th:text="${data.content}">할 일</p>
          </div>
        </div>  <!-- card 영역 끝 -->
      </div>
      <!-- 반복 영역 끝 -->

      <div class="row">   <!-- 검색 영역 시작 -->
        <div class="col-sm-2"></div>
        <div class="col-sm-8">
          <!-- form을 이용한 검색 기능 -->
          <form th:action="@{/todolist}" method="get">
            <input type="hidden" name="page" value="1">   <!-- 페이지 번호 -->
            <div class="input-group mb-3 mt-5">
              <select class="form-select" name="type" th:value="${type}">
                <option value="" th:selected="${type==''}">=== 선택하세요. ===</option>
                <option value="t" th:selected="${type=='t'}">제목</option>
                <option value="c" th:selected="${type=='c'}">내용</option>
                <option value="tc" th:selected="${type=='tc'}">제목+내용</option>
              </select>
              <input type="text" class="form-control" name="keyword" id="keyword" th:value="${keyword}">
              <button type="submit" class="btn btn-primary" name="searchButton">검색</button>
              <button type="reset" class="btn btn-primary" name="listButton" onclick="location.href='/todolist'">목록</button>
            </div>
          </form>
        </div>
        <div class="col-sm-2"></div>
      </div>    <!-- 검색 영역 끝 -->

      <!-- 페이지 영역 -->
      <div class="d-flex justify-content-center" th:if="${lastPage > 1}">
        <ul class="pagination">
          <li class="page-item" th:unless="${startPage==1}">
            <a class="page-link" th:href="@{/todolist(type=${type}, keyword=${keyword}, page=1)}">처음</a>
          </li>

          <li class="page-item" th:unless="${currentPage==1}">
            <a class="page-link" th:href="@{/todolist(type=${type}, keyword=${keyword}, page=${prevPage})}">이전</a>
          </li>

          <span th:each="page:${#numbers.sequence(startPage, endPage)}">
            <li class="page-item" th:unless="${page == currentPage}">
              <!-- 다른 페이지 -->
              <a class="page-link" th:href="@{/todolist(type=${type}, keyword=${keyword}, page=${page})}">[[${page}]]</a>
            </li>
            <li class="page-item active" th:if="${page == currentPage}">
              <!-- 활성화(현재 위치) -->
              <a class="page-link" href="#">[[${page}]]</a>
            </li>
          </span>

          <li class="page-item" th:unless="${currentPage==lastPage}">
            <a class="page-link" th:href="@{/todolist(type=${type}, keyword=${keyword}, page=${nextPage})}">다음</a>
          </li>

          <li class="page-item" th:unless="${endPage==lastPage}">
            <a class="page-link" th:href="@{/todolist(type=${type}, keyword=${keyword}, page=${lastPage})}">끝</a>
          </li>
        </ul>
      </div>
      <!-- 페이지 영역 끝 -->
      
    </div>    <!-- 본문 끝-->
    <div class="col-sm-3"></div>    <!-- 우측 여백 -->
  </div>
</div>
</body>

 

 

🟢 insert.html

<body>
<!-- content 영역 -->
<div layout:fragment="content">
  <div class="row mt-5">
    <div class="col-sm-3"></div>    <!-- 좌측 여백 -->
    <div class="col-sm-6">        <!-- 본문 시작-->

      <div class="card">
        <form th:action="@{/todoinsert}" method="post" th:object="${todoDTO}">
          <input type="hidden" name="num" th:value="*{num}">

          <ul class="list-group list-group-flush">
            <li class="list-group-item bg-light"><b>할 일 등록</b></li>
            <li class="list-group-item">
              <div class="input-group mb-3">
                <span class="input-group-text" id="title">제목</span>
                <input type="text" class="form-control" name="title">
              </div>
              <p class="text-danger" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></p>

              <div class="input-group mb-3">
                <span class="input-group-text" id="content">내용</span>
                <textarea class="form-control" name="content" rows="3"></textarea>
              </div>
              <p class="text-danger" th:if="${#fields.hasErrors('content')}" th:errors="*{content}"></p>

              <div class="input-group mb-3">
                <span class="input-group-text" id="importance">중요도</span>
                <select class="form-select" name="importance" required>
                  <option value="">===== select =====</option>
                  <option value="1">Hight</option>
                  <option value="2">Medium</option>
                  <option value="3">Low</option>
                </select>
              </div>

              <div class="input-group mb-3">
                <span class="input-group-text" id="startDay">시작일</span>
                <input type="date" class="form-control" name="startDay" required>
              </div>

              <div class="input-group mb-3">
                <span class="input-group-text" id="deadline">마감일</span>
                <input type="date" class="form-control" name="deadline" required>
              </div>
            </li>

            <li class="list-group-item">
              <button type="submit" class="btn btn-primary">등록</button>
              <button type="reset" class="btn btn-secondary">다시</button>
              <button type="button" class="btn btn-secondary" onclick="location.href='/todolist'">목록</button>
            </li>
          </ul>
        </form>
      </div>
    </div>    <!-- 본문 끝-->
    <div class="col-sm-3"></div>    <!-- 우측 여백 -->
  </div>
</div>
</body>

 

 

🟢 detail.html


<body>
<!-- content 영역 -->
<div layout:fragment="content">
  <div class="row mt-5">
    <div class="col-sm-3"></div>    <!-- 좌측 여백 -->
    <div class="col-sm-6">        <!-- 본문 시작-->
      <div class="card" th:object="${todoDTO}">
        <input type="hidden" name="num" th:value="*{num}">
        <ul class="list-group list-group-flush">
          <li class="list-group-item bg-light"><b>상세보기</b></li>
          <li class="list-group-item">
            <div class="input-group mb-3">
              <span class="input-group-text" id="title">제목</span>
              <input type="text" class="form-control" name="title" th:field="*{title}" readonly>
            </div>
            <div class="input-group mb-3">
              <span class="input-group-text" id="content">내용</span>
              <textarea class="form-control" name="content" rows="3" th:text="*{content}" readonly></textarea>
            </div>
            <div class="input-group mb-3">
              <span class="input-group-text" id="importance">중요도</span>
              <select class="form-select" name="importance" th:field="*{importance}" disabled>
                <option value="" th:selected="${todoDTO.importance==''}">===== select =====</option>
                <option value="1" th:selected="${todoDTO.importance=='1'}">Hight</option>
                <option value="2" th:selected="${todoDTO.importance=='2'}">Medium</option>
                <option value="3" th:selected="${todoDTO.importance=='3'}">Low</option>
              </select>
            </div>
            <div class="input-group mb-3">
              <span class="input-group-text" id="startDay">시작일</span>
              <input type="date" class="form-control" name="startDay" th:field="*{startDay}" disabled>
            </div>

            <div class="input-group mb-3">
              <span class="input-group-text" id="deadline">마감일</span>
              <input type="date" class="form-control" name="deadline" th:field="*{deadline}" disabled>
            </div>
          </li>
          <li class="list-group-item">
            <button type="button" class="btn btn-primary" th:onclick="|location.href='@{/todoupdate(num=${todoDTO.num})}'|">수정</button>
            <button type="button" class="btn btn-secondary" th:onclick="|location.href='@{/tododelete(num=${todoDTO.num})}'|">삭제</button>
            <button type="button" class="btn btn-secondary" onclick="location.href='/todolist'">목록</button>
          </li>
        </ul>
      </div>
    </div>    <!-- 본문 끝-->
    <div class="col-sm-3"></div>    <!-- 우측 여백 -->
  </div>
</div>
</body>

 

 

 

🟢 update.html


<body>
<!-- content 영역 -->
<div layout:fragment="content">
  <div class="row mt-5">
    <div class="col-sm-3"></div>    <!-- 좌측 여백 -->
    <div class="col-sm-6">        <!-- 본문 시작-->
      <form action="/todoupdate" method="post" th:object="${todoDTO}">
        <input type="hidden" th:field="*{num}" name="num">

        <div class="card">
          <ul class="list-group list-group-flush">
            <li class="list-group-item bg-light"><b>할 일 수정</b></li>
            <li class="list-group-item">
              <div class="input-group mb-3">
                <span class="input-group-text" id="title">제목</span>
                <input type="text" class="form-control" name="title" th:field="*{title}">
              </div>
              <p class="text-danger" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></p>

              <div class="input-group mb-3">
                <span class="input-group-text" id="content">내용</span>
                <textarea class="form-control" name="content" rows="3" th:field="*{content}"></textarea>
              </div>
              <p class="text-danger" th:if="${#fields.hasErrors('content')}" th:errors="*{content}"></p>

              <div class="input-group mb-3">
                <span class="input-group-text" id="importance">중요도</span>
                <select class="form-select" name="importance" th:field="*{importance}" required>
                  <option value="" th:selected="${todoDTO.importance==''}">=== 선택하세요. ===</option>
                  <option value="1" th:selected="${todoDTO.importance==1}">Hight</option>
                  <option value="2" th:selected="${todoDTO.importance==2}">Medium</option>
                  <option value="3" th:selected="${todoDTO.importance==3}">Low</option>
                </select>
              </div>

              <div class="input-group mb-3">
                <span class="input-group-text" id="startDay">시작일</span>
                <input type="date" class="form-control" name="startDay" th:field="*{startDay}" required>
              </div>

              <div class="input-group mb-3">
                <span class="input-group-text" id="deadline">마감일</span>
                <input type="date" class="form-control" name="deadline" th:field="*{deadline}" required>
              </div>
            </li>
            <li class="list-group-item">
              <button type="submit" class="btn btn-primary">수정</button>
              <button type="reset" class="btn btn-secondary">다시</button>
              <button type="button" class="btn btn-secondary" onclick="location.href='/todolist'">목록</button>
            </li>
          </ul>
        </div>
      </form>
    </div>    <!-- 본문 끝-->
    <div class="col-sm-3"></div>    <!-- 우측 여백 -->
  </div>
</div>
</body>

 

반응형