Pratice/CRUD
[Java+SpringBoot+JPA] To do List 게시판 만들기 (시작일/마감일 설정)
달해해
2024. 1. 9. 15:08
반응형
🟢 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>
반응형