스프링부트

김지원·2023년 7월 19일
0

HTTP통신 : stateless (다보내면 연결 끊음)

인터넷이 되는 모든 컴퓨터 = host
localhost=127.0.0.1

127.0.0.1:8080 = localhost:8080 같다

URL
프로토콜://ip주소(도메인 주소 등록했으면 일반적인 주소):프로세스포트번호(80이면 생략 가능)/경로(엔드포인트)

엔드포인트 중에서 ?뒷부분이 쿼리스트링

프로그램을 실행해서 메모리에 뜨면 프로세스

API (Application Programming Interface)
프로그램들이 소통하고 데이터를 교환할 수 있도록 정의된 인터페이스.
프로토콜이 아니라 인터페이스니까 강제성이 있다



  1. IoC(제어의 역전) 컨테이너 등록
    HomeController
    어노테이션 하면 new 안해도됨

  2. home() 요청
    유저가 주소창에 http://localhost:8080/home 입력하면 /home으로 파싱
    IoC컨테이너에서 /home으로 등록된 깃발 찾기
    호출(invoke로 메서드 이름 상관없이)

  3. home()응답
    @Controller 분석 - 리턴값을 파일로 인식 (application.yml 뷰리졸버 있어서 경로 자세히 안적어도 됨)
    @RestController 분석 - 리턴값을 데이터 자체로 인식 (메세지컨버터 동작)



헤더와 응답바디는 다 있음

Get

읽기만 (요청 body 없음)

URL 뒤에 파라미터를 붙여서 데이터를 전달하는 방식

?id=1&name=kim 이런식으로 x-www-form-urlencoded으로

URL을 보면 어떤 데이터를 전송하고자 하는지 알 수 있기 때문에 보안에 취약

읽기만 (요청 body 없음)
서버 db 내용은 그대로
Get요청은 브라우저 주소창에 직접입력, a태그 하이퍼링크, form태그 메서드를 Get으로

    <a href="http://localhost:8080/req/get">GEt요청 a태그로 하기</a>
  
    <form action="http://localhost:8080/req/get" method="get">
        <button type="submit">Get요청 form태그로 하기</button>
    </form>

Post

유저가 서버에 내용을 입력 (요청 body 있음)

BODY데이터를 보낼때 HEADER에 MIME을 적어서 데이터의 형식과 유형을 알림
(MIME : text/plain text/html image/png 등등...)

HTTP Request 헤더에 파라미터를 붙여서 데이터를 전송하는 방식
서버로 보낼 수 있는 글자수 제한 없음
GET 방식과 비교하여 보안상 약간 우위

서버 쪽에 어떤 작업을 명령할 때 사용 (데이터의 기록, 삭제, 수정 등)
클라이언트가 주소창에 적는거로는 못함 postman 사용

Post처럼 db에 변경이 있으면 유효성검사를 해야함(db에 입력되는 값이 null이거나 길이가 길거나...)

로그인인처럼 보안(패스워드)이 중요하면 select인데도 post로 http요청 본문에 데이터를 포함시켜 전송한다 (프로토콜은 무조건 지켜야 되는건 아니니까 가능은 하지만)

board/1 이런식으로 where절에 걸릴건 body가 있어도 url주소에 적히게 하고 @PathVariable로 받는게 좋다

delete

자바스크립트로 할수있음
요청 바디 없다(내용 필요없이 삭제만 하면 되니까)


@Autowired
di(주입)


클래스에
@Controller : application.yml 에 적은 경로의 파일을 줌
@RestController : return 값 자체를 줌 , @Controller 클래스일때 메서드에 @ResponseBody 붙인거랑 같음

@Autowired // IOC에서 자동으로 DI(의존성주입)

    @GetMapping("/home") // 클라이언트가 주소창에 입력하면 실행 (invoke로 실행되서 메서드 이름이랑 상관없이)
    public String home(HttpServletRequest request){
        List<Product> productList = productRepository.findAll();
        request.setAttribute("productList", productList);
        return "home"; // @Controller니까 파싱해서 뷰리졸버 경로의 파일을 실행
        // 만약 @ResponseBody를 이 메서드에 붙였다면 메세지 컨버터로 home 문자 자체를 반환했을 것
    }
    @PostMapping("/product/delete")
    public String delete(int id) {
        productRepository.deleteById(id);
        return "redirect:/";
// 리다이렉트 하지 않고 그냥  return "home";  을 하면 상품목록이 안나온다. 리퀘스트에 안담았으니까

// response.sendRedirect("/");
// 이거로도 되지만 매개변수에 HttpServletResponse response 넣고 반환타입을 void로 바꿔야함
    }
    @GetMapping("/product/{id}")
    public String detail(@PathVariable int id, HttpServletRequest request){
        System.out.println("id: "+id);
        Product product = productRepository.findById(id);
        request.setAttribute("p", product); 
// setAttribute : 서버 측에서 HTTP 요청과 응답 사이에 데이터를 전달하고 유지하기 위한 임시 저장소
        return "detail";
    }


@Entity // 이 클래스가 JPA 엔티티임을 나타냄. 데이터베이스의 테이블과 자동으로 매핑되는 객체임을 의미
@Table(name = "product_tb") // 클래스명이랑 DB랑 테이블명이 다르면 직접 매핑
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id; // @Id = PK ,  @GeneratedValue(strategy = GenerationType.IDENTITY) = AI
    private String name;
    private Integer price; // Integer라서 null값 넣어짐
    private Integer qty;

    @ManyToOne // 안적으면 오류, 상품 여러개를 판매자 1명이 팔수도 있으니까 Product가 fk, Seller가 pk
    private Seller seller;
}

롬복으로 올아귀먼트생성자 만들면 매개변수 넣는 순서가 꼬일 수 있으니까 지양하자



@Repository // 컴퍼넌트 스캔 // repository는 DAO랑 비슷한 개념
// 클래스를 IoC에 bean으로 등록하고 + 데이터베이스와의 상호작용을 처리하는 repository임을 표시
public class ProductRepository {

    @Autowired // IOC에서 자동으로 DI(의존성주입)
    private EntityManager em; // @Entity 붙은것들 불러옴

    public Product findByIdJoinSeller(int id){
        Query query = em.createNativeQuery("select * from product_tb inner join seller_tb on product_tb.seller_id = seller_tb.id where product_tb.id = :id", Product.class);
        query.setParameter("id", id);
        return (Product)query.getSingleResult();
    }


    public ProductDTO findByIdDTO(int id){
        Query query = em.createNativeQuery("select id, name, price, qty, '설명' as des from product_tb where id= :id");
        query.setParameter("id", id);

        JpaResultMapper mapper = new JpaResultMapper(); // build.gradle 에 QLRM 추가해야함
        ProductDTO productDTO = mapper.uniqueResult(query, ProductDTO.class);
        return productDTO;
    }
    
        @Transactional // db작업 전부 정상적으로 종료되면 커밋, 예외가 발생하면 롤백   /   DB 변경(C,U,D) 있는데 @Transactional 빼먹으면 오류뜸
    public void save2(String name, int price, int qty, int seller){ // 매개변수 이름이 jsp의 이름이랑도 같아야 함
        Query query = em.createNativeQuery("insert into product_tb(name, price, qty, seller_id) values(:name, :price, :qty, :seller)"); // 앞에 :붙은건 그냥 문법
        query.setParameter("name", name);
        query.setParameter("price", price);
        query.setParameter("qty", qty);
        query.setParameter("seller", seller);
        query.executeUpdate();
    }
    
        public List<Product> findAll() {
        Query query = em.createNativeQuery("select * from product_tb", Product.class); //  쿼리 결과를 Product클래스 형태로
        List<Product> productlist = query.getResultList();
        return productlist;
    }

    public Product findById(int id) {
        Query query = em.createNativeQuery("select * from product_tb where id = :id", Product.class); //  쿼리 결과를 Product클래스 형태로
        query.setParameter("id", id);
        Product product = (Product) query.getSingleResult();
        return product;
    }
}

@Repository // 컴퍼넌트 스캔 / 클래스를 IoC에 bean으로 등록하고 + 데이터베이스와의 상호작용을 처리하는 repository임을 표시함
repository는 DAO랑 비슷한 개념


@Transactional
한번에 수행되어야 할 작업
db작업 전부 정상적으로 종료되면 커밋, 예외가 발생하면 롤백
DB 변경이(C,U,D) 있으면 @Transactional 적어야함
데이터베이스의 ACID(원자성, 일관성, 고립성, 지속성) 특성을 보장하여 데이터베이스의 무결성을 유지하고, 예외 상황에 대한 데이터베이스 상태를 안전하게 복구할 수 있도록 도와줌
@Transactional이 필요한 작업 전에 미리 검사하는게 좋다.(유니크컬럼 id 같은 게 db에 있으면 당연히 터질테니까 findById로 미리 체크 해보는 식 )

동시성 관리
동시에 write하는 경우
한놈을 대기시키는 것 (고립, 락)
트랜잭션을 발동시키면 된다

트랜잭션 발동 시 문제점
지연이 발생할 수 있다
미리 조회해서 체크할 수 있는 것들은 체크해두자

ACID
원자성 (Atomicity):
트랜잭션은 하나의 논리적 작업 단위로 간주되며, 트랜잭션의 모든 연산들은 완전히 수행되거나 전혀 수행되지 않아야 합니다. 즉, 트랜잭션 내에서 하나의 연산이라도 실패하면 이전에 수행된 모든 연산들이 롤백되어 트랜잭션의 시작 상태로 되돌아가야 합니다.

일관성 (Consistency):
트랜잭션이 완료된 후에는 데이터베이스가 일관된 상태를 유지해야 합니다. 트랜잭션이 실행되기 전과 후의 데이터베이스 상태는 항상 유효한 상태여야 합니다.
트랜잭션이 진행되는 동안에 데이터베이스가 변경 되더라도 업데이트된 데이터베이스로 트랜잭션이 진행되는것이 아니라, 처음에 트랜잭션을 진행 하기 위해 참조한 데이터베이스로 진행

격리성 (Isolation):
동시에 여러 개의 트랜잭션이 실행되더라도, 각 트랜잭션은 다른 트랜잭션의 작업에 영향을 받지 않아야 합니다. 각 트랜잭션은 독립적으로 실행되는 것처럼 보여야 합니다. 이로 인해 데이터베이스에서 발생하는 병행 제어(Concurrency Control) 문제를 방지하고 데이터 일관성을 유지합니다.

지속성 (Durability):
트랜잭션이 완료된 후에는 그 결과가 영구적으로 반영되어야 합니다. 시스템 오류, 전원 이상 등의 문제로 인해 시스템이 다운되어도, 트랜잭션의 결과는 보존되어야 합니다.

textarea는 value 속성이 없음

목적지가 없으면 index파일로 간다

서블릿=톰캣이 들고있음
버퍼드리더로 읽어서 request 객체로 바꿔줌

디스패처서블릿으로 request 전달

디스패쳐서블릿
1. 클라이언트로부터 온 HTTP 요청을 파싱하고,
2. 해당 요청을 처리할 컨트롤러 메서드를 찾아주는 역할(어노테이션으로)

핵심로직 끝나면 제약조건
프런트엔드 막아야함(일반인)
백엔드도 따로 막아야함(포스트맨 등으로 비정상적인 접근)

로그인은 예외로 select인데도 post로 한다 (get은 url에 패스워드 다적히니까)


@PathVariable은 유일한거
값을 하나 받아올 때
쿼리스트링 말고 board/1/update 이런식인거 받아올때 씀
pk나 유니크나 하나만 있는거
where절에 걸릴건 @PathVariable로 받는다

    @GetMapping("/data/path/v1/{id}/{id2}") // localhost:8080/data/path/v1/11/22?a=asf
    public String pathV1(@PathVariable int id, @PathVariable int id2, String a){
        return "받은 값 : "+id+b+id2;
    }
    
    @GetMapping("/data/path/v1/{id}")
    public String pathV1(@PathVariable("id") int id){
        return "받은 값 : "+id;
    }

@RequestParam (매개변수 적을때 생략해도 자동으로 달아줌, 단 이땐 required=false (false면 null이라도 됨) 가 기본값)
board/delete?title=제목 이런식
@RequestParam(value = "keyword", required(필수여부) = false, defaultValue = "cos")

@ModelAttribute
쿼리스트링으로 받은 값이 여러개일때 모델이나 DTO로 여러개를 한번에 객체로 받을때 씀
생략가능

@RequestBody
HTTP 요청의 바디(Request Body)에 있는 데이터를 해당 메서드의 파라미터로 바인딩할 때 사용
Json(application/json) 형태의 HTTP Body를 Java 객체로 변환

json 형식과 매핑되는 dto 만들어서 받는게 좋다

Json은 폼태그로 못보냄. 자바스크립트로만 됨
json 받을땐 @RequestBody을 붙여야 함

    // JSON 받아오려면 @RequestBody
    // application/json -> {"title":"값", "content":"값"}
    @PostMapping("/data/body/v3")
    public String bodyV3(@RequestBody BoardReqDto boardReqDto) {
        System.out.println("테스트"+boardReqDto);
        return "받은 값 : " + boardReqDto.getTitle() + ", " + boardReqDto.getContent();
    }
    
    //그냥 x-www-form-urlencoded일때 
    @PostMapping("/data/body/v4")
    public String bodyV4(BoardReqDto boardReqDto) {
        return "받은 값 : " + boardReqDto.getTitle() + ", " + boardReqDto.getContent();
    }

Model
model.addAttribute()

HttpServletRequest
request.setAttribute()

둘다 컨트롤러 메서드의 매개변수에 넣어서 view로 값 보낼때 쓴다

차이점은 model에 담으면 해당 view에서만 사용 가능.

request에 담으면 더 넓은 범위에서 사용할 수 있다.
예를 들어 호출한 해당 view 페이지에서 include 한 페이지에서도 사용할 수 있다.

https://www.inflearn.com/questions/975065/request-setattribute-vs-model-addattribute
참고


HttpServletRequest : 가방 같은 역할
해당 요청 내에서만 유효함

HttpSession : 서랍 같은 역할
session
세션에 담아둔 건 계속 사용 가능

profile
https://github.com/k7850

0개의 댓글