My footsteps
게시판 만들기 본문
https://youtu.be/frI5CoZe-vE?si=8ZOQlfs8JYQin9u5
인텔리제이 자바 프로젝트 생성
(mysql 비번 12345)
MySQL 워크벤치 설치
https://velog.io/@jongjin_kim/MySQL-Workbench-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%951
MySQL Workbench DB 서버 연결
설치 완료 후 데이터 베이스 서버와 연결하는 방법과 연결하는 작업 중 발생할 수 있는 문제들을 파악하고 해결책을 제안한다.
velog.io
기존에 수업들으면서 이미 워크벤치는 깔려있어서(아니 근데 왜 mysql은 없었찌..?몰까..환경변수도 새로 추가함..) 새로 만든 내 개인 db랑 워크벤치랑 연결하려는데 어케 하는지 아리까리해서..서치해서 찾았당
(계속 이전 프로젝트창 나와서..빡챠서 ㅠㅠ 이거 설정해놓음)
4. [IntelliJ] Window에서 IntelliJ 실행시 초기 매뉴가 나오게 하기
dkyou.tistory.com
글 작성 처리
<input name="title" type="text">
<textarea name="content"></textarea>
<button type="submit">작성</button>
</form>
form으로 다 감싸주고 action으로 데이터 경로 넣어준뒤, post형식으로 보낸다
그 아래 name 지정해주는거는 .. db컬럼들이랑 일치시키나?!
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
private String content;
}
- id 어노테이션은 이 클래스의 객체가 프라이머리키(=기본키)를 갖고 있음을 나타냄. 이 클래스의 인스턴스는 데이터베이스의 레코드와 매칭된다.
- @GeneratedValue는 기본키 생성을 데이터베이스에게 위임하는 방식으로 id값을 따로 할당하지 않아도 데이터베이스가 자동으로 오토인크리먼트(=자동증감)를 하면서 기본키를 생성해준다
public String boardWritePro(String title,String content){
System.out.println("제목 : "+title);
System.out.println("내용 : "+content);
return "";
}
//위 코드에서 아래로 바꿨는데
public String boardWritePro(Board board){
System.out.println("제목 : "+board.getTitle());
return "";
}
//각각의 데이터값을 매개변수로 쓰려면 너무 많고 쓰기가 귀찮으니까 Board엔티티를 만들었기때문에 Board board로 객체 그대로 받는것
//근데 객체접근을 하려면 ~.으로 접근해야하는데 이상태로만 .으로 접근하려면 안됨. 왜냐? lombok이 없기 때문
//그래서 엔티티 클래스로 가서 @Data 어노테이션을 달아주어야 롬복을 사용할수있음(*롬복이 게터세터 다 가능하게 해줌)
//그런뒤에 .하면 get으로 가져올수있다
@Autowired는 객체를 만들수 없는 인터페이스에서도 객체를 주입받을수 있게 해주는 어노테이션이다
게시글 리스트
< 테스트 데이터 넣기 >
1. use 테이블이름; 하고 sql 실행
2.아래코드넣고sql실행DELIMITER $$ CREATE PROCEDURE testDataInsert() BEGIN DECLARE i INT DEFAULT 1; WHILE i <= 120 DO INSERT INTO board(title, content) VALUES(concat('제목',i), concat('내용',i)); SET i = i + 1; END WHILE; END$$ DELIMITER $$
3. call testDataInsert(크리에이트 프로시저옆에 적은 프로시저 네임); sql실행
- 디비에도 프로시저 데이터 잘 들어오고 했는데 타임리프로 심으니까 th:each로 심어준 데이터들이 아예 안나오길래 뭐가 문제일까..심어주는 과정이 문제일거같은데 싶어서 찾아보다가
addAttribute를 오브젝트 어쩌구로 해야하는데 아래 스트링으로 선택했어서 데이터 심어주기가 안된것..내힘으로 찾아서 쟈근 오류지만 뿌듯혀!!
게시글 상세페이지
옵셔널 오류란? 값이 있을수도 있고 없을수도 있는것
public Board boardView(Integer id){
return boardRepository.findById(id).get();
}
이 코드에서 .get을 안해주면 옵셔널 오류가 난다고 함
게시글 리스트 페이지에서 게시글 제목을 눌렀을때 그 게시글의 상세페이지로 가게하는 타임리프 코드 한줄!
1. 일단 이동을 해야하니까 a태그로
2. 그에 맞는 제목들을 보여줘야하니까 th:text title해주구
3. 해당 id에 맞는 게시글들로 이동을 하니까 th:href
4. 이동이 우선이니까 @{ }
5. 그 안에 이동 url 적어주고 @{/board/view ~~} *view=상세페이지
6. 해당 id에 맞는 상세페이지가 떠야하니까 id매칭 (id=${board.id})
게시글 삭제
public String boardDelete(Integer id){
boardService.boardDelete(id);
return "redirect:/board/list";
}
아이디를 찾아서 해당 게시글을 삭제하고 리다이렉트를 이용해 삭제 된 다음에는 페이지가 게시글 리스트 페이지로 이동하게 해줌
게시글 수정
th:value는 타임리프에서 변수나 속성값을 html요소에 삽입할때 씀
@PostMapping("/board/update/{id}")
public String boardUpdate(@PathVariable("id")Integer id,Board board){
Board boardTmp = boardService.boardView(id);
boardTmp.setTitle(board.getTitle());
boardTmp.setContent(board.getContent());
boardService.write(boardTmp);
return "redirect:/board/list";
}
- 새로 값을 전달하면서 기존에 있던걸 변경하는거니까 post매핑
- 기존에 있던 게시글 내용이 필요하니까 Board객체를 인자로 받음
- 게시글에서(=boardView) 아이디값을 인자로 받아서 Board객체인 boardTmp에 넣어준다 이과정에서 이전 내용이 수정시에도 보여질수있게 되는것
- set으로 값을 세팅해줌으로써 새로이 수정된 제목과 내용을 boardTmp에 세팅,즉 담아준다
- write메서드를 호출하면서 모든 수정된 내용이 저장된 boardTmp를 DB에도 save될수 있게끔 해줌
public void write(Board board){
boardRepository.save(board);
}
- 마지막으로 수정하기 버튼을 누르면 게시글리스트 페이지로 가게끔 리다이렉트
메세지 띄우기
th:inline="javascript"는 서버측 데이터를 자바스크립트 코드에 포함할수 있도록 허용해주는 타임리프 구문이다.
이렇게 하면 서버측 데이터를 동적으로 html 페이지에 표시하거나 자바스크립트 코드에서 사용할수 있다.
/*<![CDATA[*/
var message = [[${message}]]; 메세지를 담아서
alert(message); 메세지를 얼럿 창으로 띄우고
location.replace([[${searchUrl}]]); 페이지 이동
/*]]>*/
</script>
@PostMapping("/board/writepro")
public String boardWritePro(Board board,Model model){
boardService.write(board);
model.addAttribute("message","글작성이 완료되었습니다.");
model.addAttribute("searchUrl","/board/list");
return "mesage";
}
모델인자로 받고 모델에 html에서 자스로 심어준 메세지와 서치url을 넣어주면 mesage.html이 띄워지면서 url은 list페이지로 가게된다. 짱신기..
파일 업로드
파일업로드시에 있어야할 html태그 속성
이거랑,
이고!
public void write(Board board, MultipartFile file)throws Exception{
// 1.경로지정
String projectPath = System.getProperty("user.dir") + "\\src\\main\\resources\\static\\files";
// 2.이름지정
UUID uuid = UUID.randomUUID(); //랜덤으로 이름 지어줌
String fileName = uuid + "_" + file.getOriginalFilename();
// 파일을 생성해줄건데 경로는 projectPath 이름은 fileName변수
File saveFile = new File(projectPath,fileName);
file.transferTo(saveFile);
board.setFilename(fileName); // 실제 저장될 파일 이름
board.setFilepath("/files/"+fileName); // 실제 저장될 파일 경로 이 두개의 과정을 하면 DB에 실제로 저장됨
boardRepository.save(board);
}
페이징 처리
@GetMapping("/board/list")
public String boardList(Model model, @PageableDefault(page = 0,size = 10,sort = "id",direction = Sort.Direction.DESC)Pageable pageable){
model.addAttribute("list",boardService.boardList(pageable));
return "boardlist";
}
- page(디폴트 페이지) size(한페이지에 보여줄 글수) sort(정렬기준) direction(어떻게 정렬할건지)
- Pageable은 인턴페이스임
(*아 자꾸 다 철자 똑같은데 오류나서 보니까 역시 임포트 문제 ㅋㅋㅋㅋㅋㅋㅋ Pageable은 자바 어쩌구 말고 스프링어쩌구로 임포트 해야함!!!)
public Page<Board> boardList(Pageable pageable){
return boardRepository.findAll(pageable);
}
서비스에서도 페이저블을 넣어주면 된다. 원래 반환타입이 List<Board>였는데 이건 매개인자가 없을때만 리스트 형식으로 반환해주는거고, 페이저블을 인자로 넣어줬기 때문에 반환타입도 Page<Board>가 되어야 한다.
@GetMapping("/board/list")
public String boardList(Model model, @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.DESC)Pageable pageable){
Page<Board> list = boardService.boardList(pageable);
int nowPage = list.getPageable().getPageNumber()+1; // 페이저블에서 넘어온 현재 페이지 가져오기(페이지가 0부터 시작하니까 +1 해줌)
int startPage = Math.max(nowPage-4,1);
// 총페이지는 6페이지로 설정할꺼라서 -4. 그리고 만약 페이지가 1일때 1-4=-3이니까 마이너스값이 안나오게끔, 둘중 더 큰수를 반환하게끔 Math.max 사용
int endPage = Math.min(nowPage+5,list.getTotalPages());
// 설정한 토탈페이지보다 큰수가 나오면 안되니까 왼쪽인자가 토탈페이지보다 크다면 그보다 작은 오른쪽 즉,토탈페이지를 반환할수있게 Math.min사용
model.addAttribute("list",list);
model.addAttribute("nowPage",nowPage);
model.addAttribute("startPage",startPage);
model.addAttribute("endPage",endPage);
return "boardlist";
}
<th:block th:each="page : ${#numbers.sequence(startPage,endPage)}">
<a th:if="${page != nowPage}" th:href="@{/board/list(page = ${page-1})}" th:text="${page}"></a>
<strong th:if="${page == nowPage}" th:text="${page}" style="color : red"></strong>
</th:block>
- th:block는 그냥 그룹화할때 쓰는것. html의 section같은거다
- numbers앞에 #을 붙이는 이유는 타임리프에서 제공하는 숫자 관련 유틸리티 객체를 나타낼때...
<a th:if="${page != nowPage}" th:href="@{/board/list(page = ${page-1})}" th:text="${page}"></a>
// th:if="${page != nowPage} 현재페이지가 아닐때.
// th:href="@{/board/list(page = ${page-1})}" 이전페이지의 링크 생성
// 예를들어 내가 지금 3페이지면 3에서 1을 뺀 2페이지를 화면에도 보이겠다는것..
// th:text="${page}" 현재 페이지 띄우기
검색
public interface BoardRepository extends JpaRepository<Board,Integer> {
//제네릭 타입의 첫번째 인자는 엔티티이름,두번째 인자는 프라이머리키의 자료형 타입
Page<Board> findByTitleContaining(String searchKeyword, Pageable pageable);
}
이런식으로 레포지토리에 작성하고
public Page<Board> boardSerchList(String searchKeyword,Pageable pageable){
return boardRepository.findByTitleContaining(searchKeyword,pageable);
}
서비스에서도 인자 맞춰서
컨트롤러에는 인자로 String searchKeyword 넣어주고
if(searchKeyword == null){
list = boardService.boardList(pageable);
}else{
list = boardService.boardSerchList(searchKeyword,pageable);
}
이로직만 상단에 추가해줬다. list 초기화한건 조건문에 자꾸 쓰면 반복되서 null로 초기화하고
검색어가 없으면 그냥 페이징만된 리스트 페이지를 리스트에 넣고,그렇지 않고 검색어가 있으면 페이징도되고 검색어도 찾아서 리스트에 넣어주는 로직
th:href="@{/board/list(page = ${page-1}, searchKeyword = ${param.searchKeyword})}"
// list.html의 이동부분에서 searchKeyword = ${param.searchKeyword}를 넣어주면 파라미터에 검색어를 담아서
// 1페이지에 해당되는 검색어와 2페이지에 해당되는 검색어 모두 검색이 가능하게 된다)
// 원래는 해당 검색어가 2페이지까지 잇어도 1페이지만 보이고 2페이지를 누르면 그냥 전체게시글의 2페이지를 보여줬었음(검색어포함X)
최종적으로 html에 이것까지 넣어주면 검색창으로 검색하기 완성!
<input type="text" name="searchKeyword">
<button type="submit">검색</button>
</form>
- 데이터를 변경할 필요가아니라 그냥 검색된거 보여주기만 하면 되니까 get
- 리스트 페이지로 보여줘야하니까 액션은 리스트 페이지
- input창에서 searchKeyword 변수로 인식시켜야하니까 name= searchKeyword
- 검색결과를 보내야하니까 버튼은 서브밋으루~~
'Develop > 곤부📙' 카테고리의 다른 글
김영한 '스프링 입문' / 4 (0) | 2023.10.03 |
---|---|
김영한 '스프링 입문' / 3 (0) | 2023.09.30 |
김영한 '스프링 입문' / 2 (0) | 2023.09.14 |
Vue Router (0) | 2023.09.13 |
김영한 '스프링 입문' / 1 (0) | 2023.09.13 |