inblog logo
|
jay0628
    SpringBoot

    [Spring Boot] 9. 스프링부트 블로그 v1

    김주희's avatar
    김주희
    Mar 17, 2025
    [Spring Boot] 9. 스프링부트 블로그 v1
    ❗
    처음부터 따라할 수 있도록! 기록해두기
    프로젝트 생성
    notion image
    notion image
    notion image
    notion image
    필기
    hdd에 영구히 저장할 수 있도록 도와주는기술 jpa -> db에
    {{> layout/header}} layout 폴더에 있는 header : 부분 템플릿
    @PathVariable 주소에 들어오는 숫자값을 annotation으로 int id에 넣어줄 수 있음 (받을 수 있음) 괄호 안의 변수와 중괄호 안의 변수가 같아야 한다.
    bean = new된 객체
    파일이 날라가는게 아니니까 확장자를 쓰지X -> 버퍼에 심어줘야 header의 content-type에 mime 타입
    content-type는 header의 key, mime 타입은 key의 value의 형식 담긴 내용의 형식이 어떤건지 mime 타입을 지켜서 content-type로 지정해줘야한다.
    1. 쿼리스트링 -> 주소 (DB에서 where절에 쓴다 - 구체적 질의 ?key=value&key=value
    1. 바디(Post,Put) - xwform ( insert 하려고 key=value&key=value
    post를 insert,update,delete로 쓸 예정이라서 save-form에서 "/board"가 아닌 action="/board/save"로 한다. -> 주소에 delete같은 동사 붙이면 안되지만 post로만 할거라서 붙인다! form 태그는 get,post 밖에 안됨
    수행해달라(->끝나면 redirection 어디 페이지로 갈지) = action
    <정리> GET /emp/1 select * from emp where empno = 1; pk나 unique는 주소에 /를 바로
    GET /emp?job=manager select * from emp where job = "MANAGER"' pk,unique가 아니면 모두 queryString
    GET /emp?job=manager&sal=1000 select * from emp where job = "MANAGER"' and sal = 1000; -> int sal로 받으면 안되고 integer로 받아야 null처리 가능
    annotation spring이 제어권 가지고 new -> 제어의 역전 ioc
    EntityManager JDBC connection 객체 @Controller @Repository -> 내부에 @Component 잇음 ~ 설정 파일의 db연결 코드 읽으면서 ioc에 EntityManager 생김(? ~ @entity 찾아서 query로 바꿔서 실행 ~ DB에 board_tb 생김(IoC에 뜬게 아님) ~ IoC자기자신을 순회하면서 EntityManager type으로 찾음 잇네? -> 주입 해줌 (의존성 주입)
    repository는 entityManager 의존한다. service는 repository 의존 controller는 service 의존
    게시글 쓰기 완료 후 list로 가면 모델에 데이터 안담고 감 -> list에 안뿌려짐 (reudirection 해야하는 이유)
    getRequestDispatcher
    model은 request라고 생각
    class Model{ HttpServlet request;
    void setAttribute(key,value) }
    dataTransferObject DTO
    write -> 다른 사람 wait 하도록 만듦 delete -> 없는 게시글 굳이 삭제하겠다고 고립시킬 필요 없음 -> 조회해서 있는지 확인 없으면 실패하는 시간동안 고립됨
    update는 model에서 조회해서 뿌리고 수정해야됨
     
     
    application.properties
    # utf-8 한글 인코딩 server.servlet.encoding.charset=utf-8 server.servlet.encoding.force=true # DB 연결 코드 (EntityManager 만들어냄) spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true # JPA @Entity 스캔해서 테이블 생성 spring.jpa.hibernate.ddl-auto=create # 콘솔에 쿼리 표시 spring.jpa.show-sql=true # 더미데이터 sql문 실행 spring.sql.init.data-locations=classpath:db/data.sql # ddl-auto가 실행된 후에 sql문 실행하는 법 spring.jpa.defer-datasource-initialization=true # mustache에서 request 객체 접근하게 설정하는 법 spring.mustache.servlet.expose-request-attributes=true
     
     
    package com.metacoding.blogv1.board; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import java.sql.Timestamp; // runtime 때 만들어줌 @Getter // (Lombok) @AllArgsConstructor // full 생성자 @NoArgsConstructor // default 생성자 만들어줌 (Lombok) @Table(name = "board_tb") // table명 설정 @Entity // jpa가 관리할 수 있게 설정 public class Board { @Id // pk 설정 @GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment 설정 private Integer id; private String title; private String content; private Timestamp createdAt; // now }
    package com.metacoding.blogv1.board; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import java.util.List; // 책임 : 요청 잘 받고 응답 잘 하고 @Controller // 컴퍼넌트 스캔 -> DS가 활용 public class BoardController { // DI private BoardService boardService; public BoardController(BoardService boardService) { this.boardService = boardService; } @PostMapping("/board/{id}/update") public String update(@PathVariable("id") int id, String title, String content) { // update board_tb set title(body로 받음)=?, content(body로 받음)=? where id(주소로 받음) =? // 주소로 받는 데이터는 전부 where에 걸린다. (pk,unique는 /1로, queryString return "redirect:/board/" + id; } @PostMapping("/board/{id}/delete") // 원래는 DeleteMapping이지만 JS를 안배워서 PostMapping으로 public String delete(@PathVariable("id") int id) { // System.out.println("id : " + id); // id로 db 가서 삭제하면 됨 -> 이건 controller의 책임이 아님 boardService.게시글삭제(id); // return 받을게 없음 return "redirect:/"; } @PostMapping("/board/save") public String save(String title, String content) { // input 태그의 name값과 같아야 한다. // -> req.getParameter()와 같다 // return "list"; -> 안되는 이유) db 연결 후 데이터 뿌릴때 문제 생김(db 데이터가 안들어감) - 모델이 데이터 안담고 간다 boardService.게시글쓰기(title, content); return "redirect:/"; // page로 가는 주소가 만들어져 있으면 무조건 redirection (재활용 하세용) } // c - model - v @GetMapping("/") public String list(HttpServletRequest request) { // DB 조회해서 화면에 뿌려줘야 됨 -> service에게 요청 List<Board> boardList = boardService.게시글목록(); // boardList는 model이다 request.setAttribute("models", boardList); // request에 담기 return "list"; // forward하기 // = req.getRequestDispatcher("list").forward(req,resp) } // model을 request에 담아야지 -> @GetMapping("/board/{id}") // 패턴 매칭 /board/1, /board/2 public String detail(@PathVariable("id") int id, HttpServletRequest request) { Board board = boardService.게시글상세보기(id); // board는 model이다 request.setAttribute("model", board); return "detail"; } // c - v @GetMapping("/board/save-form") // 주소 (하이픈(-) 사용) public String saveForm() { // 자바는 카멜 표기법ㅇ, url에는 _,카멜 안씀 return "save-form"; // 확장자 없어도 되는 이유 - 무조건 viewResolver를 탄다! } // ex)게시글 3번 수정하기 @GetMapping("/board/{id}/update-form") // URI public String updateForm(@PathVariable("id") int id) { return "update-form"; // 파일명 } }
    package com.metacoding.blogv1.board; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; // 책임: 트랜잭션 처리, 비지니스 로직 처리 (ex)송금 중 잔액 검사) @Service // IoC public class BoardService { private BoardRepository boardRepository; // DI : 의존성 주입 -> IoC로부터 들고옴 (BR는 반드시 IoC에 떠있어야 함) public BoardService(BoardRepository boardRepository) { this.boardRepository = boardRepository; } // boardRepository는 ioc에 떠있으니까 DI받아야됨 @Transactional // 트랜잭션 시작 -> 함수 내부가 다 수행되면 commit, 실패 rollback public void 게시글쓰기(String title, String content) { boardRepository.insert(title, content); } // write(insert,update,delete)만 commit/rollBack -> 트랜잭셔널 없어도 됨 public List<Board> 게시글목록() { List<Board> boardList = boardRepository.findAll(); return boardList; // 그냥 boardRepository.findAll() 바로 리턴해도 됨 할 일 없으니까 바로 위임 가능 } public Board 게시글상세보기(int id) { return boardRepository.findById(id); // 할 일 없으니까 바로 위임 } @Transactional public void 게시글삭제(int id) { // 1. 게시글이 존재하는지 확인 Board board = boardRepository.findById(id); // 2. 삭제 if (board == null) { throw new RuntimeException("게시글이 없는데 왜 삭제를 ㅠㅠ"); } boardRepository.deleteById(id); } // commit (exception 터지면 rollback) }
    package com.metacoding.blogv1.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import org.springframework.stereotype.Repository; import java.util.List; // 책임 : DB와 소통하는 친구 @Repository // IoC 컬렉션에 뜬다. (BoardController도 IoC에 뜬다 public class BoardRepository { private EntityManager em; // DI -> IoC 순회해서 EntityManager 타입으로 찾아서 전달해준다. public BoardRepository(EntityManager em) { System.out.println("BoardRepository new 됨"); this.em = em; } public void insert(String title, String content) { Query query = em.createNativeQuery("insert into board_tb(title, content, created_at) values(?,?,now())"); query.setParameter(1, title); query.setParameter(2, content); query.executeUpdate(); // insert,update,delete } public List<Board> findAll() { // Board.class 추가 -> board class로 자동매핑 Query query = em.createNativeQuery("select * from board_tb order by id desc", Board.class); List<Board> boardList = query.getResultList(); // 조회할 때 return boardList; } public Board findById(int id) { Query query = em.createNativeQuery("select * from board_tb where id = ?", Board.class); query.setParameter(1, id); try { Board board = (Board) query.getSingleResult(); // getSingleResult의 return 타입이 Object이므로 다운캐스팅해야됨 return board; } catch (Exception e) { return null; } } public void deleteById(int id) { Query query = em.createNativeQuery("delete from board_tb where id = ?"); // 조회하는 게 아니므로 매핑 필요 없음 query.setParameter(1, id); query.executeUpdate(); } }
     
    data.sql
    insert into board_tb(title, content, created_at) values ('제목1', '내용1', now()); insert into board_tb(title, content, created_at) values ('제목2', '내용2', now()); insert into board_tb(title, content, created_at) values ('제목3', '내용3', now()); insert into board_tb(title, content, created_at) values ('제목4', '내용4', now()); insert into board_tb(title, content, created_at) values ('제목5', '내용5', now());
     
    detail.mustache
    {{> layout/header}} <section> <a href="/board/{{model.id}}/update-form">수정화면으로 가기</a> <form action="/board/{{model.id}}/delete" method="post"> <button type="submit">삭제</button> </form> <div> 번호 : {{model.id}} <br> 제목 : {{model.title}} <br> 내용 : {{model.content}} <br> 작성일 : {{model.createdAt}} <br> </div> </section> </body> </html>
     
    list.mustache
    {{> layout/header}} <section> <table border="1"> <tr> <th>번호</th> <th>제목</th> <th></th> </tr> {{#models}} <tr> <td>{{id}}</td> <td>{{title}}</td> <td><a href="/board/{{id}}">상세보기</a></td> </tr> {{/models}} </table> </section> </body> </html>
     
    save-form.mustache
    {{> layout/header}} <section> <!-- http body : title=제목6&content=내용6 http header : application/x-www-form-urlencoded key값은 input태그의 name, value값은 input태그에 사용자가 입력하는 값--> <form action="/board/save" method="post" enctype="application/x-www-form-urlencoded"> <input type="text" name="title" placeholder="제목"><br> <input type="text" name="content" placeholder="내용"><br> <button type="submit">글쓰기</button> </form> </section> </body> </html>
     
    update-form.mustache
    {{> layout/header}} <section> <form action="/board/1/update" method="post" enctype="application/x-www-form-urlencoded"> <input type="text" name="title" value="제목1"><br> <input type="text" name="content" value="내용1"><br> <button type="submit">글수정</button> </form> </section> </body> </html>
     
    Share article

    jay0628

    RSS·Powered by Inblog