[Java+SpringBoot+JPA] 기본 CRUD 구현하기 (6) 조회수 증가 기능

2024. 1. 2. 17:31Pratice/CRUD

반응형

 

 

 

🟢 공지사항 유형 선언

마우스 우클릭 → 생성  → 생성자/get

public enum NoticeRole {

  PRODUCT("상품"),
  EVENT("이벤트"),
  DELIVERY("배송"),
  CHANGE("교환/환불"),
  ETC("기타");

  private final String description;

  //생성자
  NoticeRole(String description) {
    this.description = description;
  }

  //Getter
  public String getDescription() {
    return description;
  }

}

 

 

 

🟢 Entity 생성

@Entity
@Getter @Setter
@AllArgsConstructor @NoArgsConstructor
@Builder
@Table(name = "notice")
@SequenceGenerator(
    name = "notice_SEQ",
    sequenceName = "notice_SEQ",
    initialValue = 1,
    allocationSize = 1)

public class NoticeEntity extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notice_SEQ")
  private Integer noticeNum;

  @Enumerated(EnumType.STRING)
  private NoticeRole noticeRole;

  @Column(name = "noticeTitle", length = 30, nullable = false)
  private String noticeTitle;

  @Column(name = "noticeWriter", length = 30, nullable = false)
  private String noticeWriter;

  @Lob
  @Column(name = "noticeContent", length = 30000, nullable = false)
  private String noticeContent;

  @Column(name = "noticeView")
  private Integer noticeView;

}

 

 

 

🟢 DTO 생성

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NoticeDTO {

  private Integer noticeNum;

  private NoticeRole noticeRole;

  @NotEmpty(message = "제목은 반드시 작성해야합니다.")
  private String noticeTitle;

  @NotEmpty(message = "작성자는 반드시 작성해야합니다.")
  private String noticeWriter;

  @NotEmpty(message = "내용은 반드시 작성해야합니다.")
  private String noticeContent;

  private Integer noticeView;

  private LocalDateTime reDate;

  private LocalDateTime moDate;

}

 

 

 

🟢 Repository 생성

 ▪️ 검색조건

@EnableJpaRepositories
public interface NoticeRepository extends JpaRepository <NoticeEntity, Integer> {

  //검색조건 (카테고리 유형, 제목, 제목+내용)
  @Query("SELECT n FROM NoticeEntity n WHERE n.noticeRole = :noticeRole")
  Page<NoticeEntity> findByNoticeRole (@Param("noticeRole")NoticeRole noticeRole, Pageable pageable);

  @Query("SELECT n FROM NoticeEntity n WHERE n.noticeTitle Like %:keyword%")
  Page<NoticeEntity> findByNoticeTitle (@Param("keyword")String keyword, Pageable pageable);

  @Query("SELECT n FROM NoticeEntity n WHERE n.noticeTitle Like %:keyword% OR n.noticeContent Like %:keyword%")
  Page<NoticeEntity> findByNoticeTitleAndAndNoticeContent (@Param("keyword")String keyword, Pageable pageable);

}

 

▪️ 조회수 증가

  - @Modifying : 해당 메서드가 데이터를 수정하는 업데이트 작업을 수행

  - 반환 타입 void : 데이터조회가 아닌 데이터를 수정하는 업데이트 쿼리이기 때문

  - @Parm : 쿼리에서 사용되는 매개변수의 이름을 지정

  //조회수증가
  @Modifying
  @Query("UPDATE NoticeEntity n SET n.noticeView = n.noticeView + 1 WHERE n.noticeNum = :noticeNum")
  void noticeView(@Param("noticeNum") Integer noticeNum);

 

 

 

🟢 Service 생성

▪️ 조회수 증가

  //조회수 증가
  public void viewCount(Integer noticeNum) throws Exception {

    noticeRepository.noticeView(noticeNum);
    
  }
더보기
@Service
@Transactional
@RequiredArgsConstructor
public class NoticeService {

  private final NoticeRepository noticeRepository;
  private final ModelMapper modelMapper = new ModelMapper();

  //삽입
  public NoticeEntity insert (NoticeDTO noticeDTO) throws Exception {

    NoticeEntity noticeEntity = modelMapper.map(noticeDTO, NoticeEntity.class);
    noticeRepository.save(noticeEntity);

    return noticeEntity;

  }


  //조회(전체 - 검색조건, 페이지)
  public Page<NoticeDTO> findAll (String type, String keyword, String noticeRole, Pageable page) throws Exception {

    int currentPage = page.getPageNumber()-1;
    int blockLimit = 10;

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

    Page<NoticeEntity> noticeEntityPage;

    if("ca".equals(type) && noticeRole != null && !noticeRole.isEmpty()) {
      noticeEntityPage = noticeRepository.findByNoticeRole(NoticeRole.valueOf(noticeRole), pageable);
    } else if ("t".equals(type) && keyword != null && !keyword.isEmpty()) {
      noticeEntityPage = noticeRepository.findByNoticeTitle(keyword, pageable);
    } else if("tc".equals(type) && keyword != null && !keyword.isEmpty()) {
      noticeEntityPage = noticeRepository.findByNoticeTitleAndAndNoticeContent(keyword, pageable);
    } else {
      noticeEntityPage = noticeRepository.findAll(pageable);
    }

    List<NoticeDTO> noticeDTOList = noticeEntityPage.stream()
        .map(noticeEntity -> modelMapper.map(noticeEntity, NoticeDTO.class))
        .collect(Collectors.toList());

    return new PageImpl<>(noticeDTOList, pageable, noticeEntityPage.getTotalElements());

  }


  //조회(개별)
  public NoticeDTO findOne (Integer noticeNum) throws Exception {

    Optional<NoticeEntity> noticeEntityOptional = noticeRepository.findById(noticeNum);
    NoticeDTO noticeDTO = modelMapper.map(noticeEntityOptional, NoticeDTO.class);
    return  noticeDTO;

  }



  //수정
  public void update(NoticeDTO noticeDTO) throws Exception {

    Integer noticeNum = noticeDTO.getNoticeNum();
    Optional<NoticeEntity> noticeEntity = noticeRepository.findById(noticeNum);

    if(noticeEntity.isPresent()) {
      NoticeEntity update = noticeEntity.get();
      modelMapper.map(noticeDTO, update);

      noticeRepository.save(update);
    }
  }


  //삭제
  public void delete (Integer noticeNum) throws Exception {

    noticeRepository.deleteById(noticeNum);

  }
  
}

 

 

 

🟢 Controller 생성

▪️ 개별조회 (조회수 증가)

  //개별조회
  @GetMapping("/noticedetail")
  public String findOne (Integer noticeNum, Model model) throws Exception {

    NoticeDTO noticeDTO = noticeService.findOne(noticeNum);

    noticeService.viewCount(noticeNum);
    model.addAttribute("noticeDTO", noticeDTO);

    return  "notice/detail";

  }
더보기
@Controller
@RequiredArgsConstructor
public class NoticeController {
  
  private final NoticeService noticeService;
  
  //삽입폼
  @GetMapping("/noticeinsert")
  public String insertForm (Model model) throws Exception {

    NoticeDTO noticeDTO = new NoticeDTO();
    model.addAttribute("noticeDTO", noticeDTO);
    model.addAttribute("noticeRole", NoticeRole.values());

    return "/notice/insert";

  }
  //삽입처리
  @PostMapping("/noticeinsert")
  public String insertProc (@Valid NoticeDTO noticeDTO, BindingResult bindingResult, Model model) throws Exception {

    if(bindingResult.hasErrors()) {
      model.addAttribute("noticeRole", NoticeRole.values());
      return "/notice/insert";
    } noticeService.insert(noticeDTO);

    return "redirect:/noticelist";

  }


  //전체조회
  @GetMapping("/noticelist")
  public String noticelist (@RequestParam(value = "type", defaultValue = "") String type,
                            @RequestParam(value = "keyword", defaultValue = "" ) String keyword,
                            @RequestParam(value = "noticeRole", defaultValue = "") String noticeRole,
                            @PageableDefault(page=1) Pageable pageable,
                            Model model) throws Exception {

    Page<NoticeDTO> noticeDTOS = noticeService.findAll(type, keyword, noticeRole, pageable);

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

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

    model.addAttribute("noticeDTOS", noticeDTOS);

    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 "/notice/list";

  }


  //개별조회
  @GetMapping("/noticedetail")
  public String findOne (Integer noticeNum, Model model) throws Exception {

    NoticeDTO noticeDTO = noticeService.findOne(noticeNum);

    noticeService.viewCount(noticeNum);
    model.addAttribute("noticeDTO", noticeDTO);

    return  "notice/detail";

  }
  
  
  //수정폼
  @GetMapping("/noticeupdate")
  public String updateForm (Integer noticeNum, Model model) throws Exception {

    NoticeDTO noticeDTO = noticeService.findOne(noticeNum);
    model.addAttribute("noticeDTO", noticeDTO);
    model.addAttribute("noticeRole", NoticeRole.values());

    return "notice/update";

  }
  //수정처리
  @PostMapping("/noticeupdate")
  public String updateProc (@Valid NoticeDTO noticeDTO, BindingResult bindingResult, Model model) throws Exception {

    if(bindingResult.hasErrors()) {
      model.addAttribute("noticeRole", NoticeRole.values());
    } noticeService.update(noticeDTO);

    return "redirect:/noticelist";

  }


  //삭제
  @GetMapping("/noticedelete")
  public String deleteForm(Integer noticeNum) throws Exception {

    noticeService.delete(noticeNum);
    return "redirect:/noticelist";

  }

}

 

 

🟢 insert.html

조회수 0으로 초기화

<input type="hidden" name="noticeView" value=0>

 

 

 

🟢 list.html

▪️ 조회수 출력

<td th:text="${data.noticeView}">조회수</td>

 

▪️ 검색기능

더보기
<form th:action="@{/noticelist}" method="get">
  <input type="hidden" name="page" value="1">
  <div class="input-group mb-3 mt-5">
    <select class="form-select" name="type" id="searchType">
      <option value="" th:selected="${type == ''}">== 선택 ==</option>
      <option value="t" th:selected="${type == 't'}">제목</option>
      <option value="tc" th:selected="${type == 'tc'}">제목+내용</option>
      <option value="ca" th:selected="${type == 'ca'}">카테고리</option>
    </select>

    <!-- 중분류 (검색창) -->
    <input type="text" class="form-control" name="keyword" id="keyword" th:value="${keyword}">

    <!-- 중분류 (카테고리 유형) -->
    <select class="form-select" name="noticeRole" id="noticeRole">
      <option value="" th:selected="${noticeRole == ''}"> == 선택 == </option>
      <option value="PRODUCT" th:selected="${noticeRole == 'PRODUCT'}"> 상품 </option>
      <option value="EVENT" th:selected="${noticeRole == 'EVENT'}"> 이벤트 </option>
      <option value="DELIVERY" th:selected="${noticeRole == 'DELIVERY'}"> 배송 </option>
      <option value="CHANGE" th:selected="${noticeRole == 'CHANGE'}"> 교환/환불 </option>
      <option value="ETC" th:selected="${noticeRole == 'ETC'}"> 기타 </option>
    </select>

    <button type="submit" class="btn btn-primary" name="searchButton">검색</button>

    <button type="reset" class="btn btn-primary" name="resetButton" th:onclick="|location.href='@{/noticelist}'|">다시</button>

  </div>
</form>

<!-- JavaScript 코드 -->
<script>

    // resetSearchForm : 검색 유형 드롭다운이 변경될 때 호출되며, 선택한 검색 유형에 따라 스타일을 업데이트
    document.getElementById('searchType').addEventListener('change', function () {
        resetSearchForm();
    });

    function resetSearchForm() {
        // 검색 폼 값 초기화
        document.getElementById('keyword').value = '';
        document.getElementById('noticeRole').value = '';

        // localStorage에서 이전 선택 값을 제거
        localStorage.removeItem('selectedNoticeRole');

        // applyStylesAfterSearch : 선택한 검색 유형 및 카테고리 유형에 따라 스타일을 적용
        applyStylesAfterSearch();
    }

    function applyStylesAfterSearch() {
        var selectedSearchType = document.getElementById('searchType').value;

        //toggleElementDisplay : 조건에 따라 요소의 표시 여부를 전환
        //'ca'가 선택되면 카테고리 유형을 선택하는 드롭다운 출력
        toggleElementDisplay('noticeRole', selectedSearchType == 'ca');

        //'n'이 선택되면 상품명을 입력하는 텍스트 필드 출력
        toggleElementDisplay('keyword', selectedSearchType == 't' || selectedSearchType == 'tc');

        //localStorage : 검색 값 유지
        if (selectedSearchType == 'ca') {
            //초기화
            var noticeRoleElement = document.getElementById('noticeRole');

            if (noticeRoleElement.value == '') {
                // localStorage에서 이전 선택 값을 가져옴
                noticeRoleElement.value = localStorage.getItem('selectedNoticeRole') || '';
            }
        }
    }


    //검색창 또는 선택창 출력 (elementId에 해당하는 요소의 표시 여부를 condition에 따라 조절)
    function toggleElementDisplay(elementId, condition) {
        var element = document.getElementById(elementId);
        element.style.display = condition ? 'block' : 'none';
    }

    // 페이지 로드 시 초기 스타일 적용
    applyStylesAfterSearch();

    // 페이지 로드 시 localStorage에서 이전 선택 값을 가져와 적용
    window.onload = function () {
        applyStylesAfterSearch();
    }

    // 페이지 언로드 시 localStorage에 현재 선택 값을 저장
    window.onbeforeunload = function () {
        localStorage.setItem('selectedNoticeRole', document.getElementById('noticeRole').value);
    }

</script>

 

 

 

🟢 detail.html

<input type="hidden" name="noticeView" th:value="${noticeDTO.noticeView}">

 

 

 

🟢 update.html

<input type="hidden" name="noticeView" th:value="*{noticeView}">

 

 

 

 

반응형