스프링 입문_1-4

5w31892p·2022년 12월 5일
0

Spring

목록 보기
1/30

📜 Spring 입문 강의

프로토콜

  • 메시지 송/수신자가 서로 필요한 요청과 응답할 수 있도록 미리 규약해 놓은 것

HTTP (HyperText Transfer Protocol)

  • 데이터 주고 받는 양식을 정의한 통신 규약 중 하나
  • 브라우저가 Request(요청)하면, 서버가 Response(응답)

API(application programming interface)

  • 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙
  • 웹 API는 클라이언트와 웹 리소스 사이의 게이트웨이

인터페이스(interface)

  • 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면
  • 즉, 사용자가 기기를 쉽게 동작시키는데 도움을 주는 시스템

RESTful API

두 컴퓨터 시스템이 인터넷을 통해 정보를 안전하게 교환하기 위해 사용하는 인터페이스

Representational State Transfer(REST)

  • API 작동 방식에 대한 조건을 부과하는 소프트웨어 아키텍처
    • REST 아키텍처 스타일을 따르는 API를 REST API
    • REST 아키텍처를 구현하는 웹 서비스를 RESTful 웹 서비스

서버 개발에서 가장 많이 하는 일

  • 새로운 정보와 기존 정보를 가지고 정해진 로직을 수행하는 일

:: software design pattern

  • 소프트웨어 공학의 소프트웨어 디자인에서 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결

:: 서버

  1. 새로운 데이터 처리하는 부분
  2. 서비스 로직 처리하는 부분
  3. 기존 데이터를 이용하는 부분

:: 레이어드 아키텍처 패턴

1. Presentation 계층

  • 사용자와 상호 작용 처리 계층
  • CLI, HTTP 요청, HTML 처리 등을 담당한다.
  • HTTP 요청 처리 및 HTML 렌더링에 대해 알고 있는 웹 계층
  • 흔히 말하는 MVC (Model / View / Controller) 도 이 계층에 속함
  • 스프링에서는 @Controller 어노테이션을 사용하여 표현

2. Domain(Business or Service) 계층

  • 서비스/시스템의 핵심 로직
  • 유효성 검사 및 계산을 포함하는 Business 논리 계층
  • 애플리케이션이 수행해야하는 도메인과 관련된 작업들을 담당
  • 입력/저장된 데이터 기반으로 계산
  • Presentation 계층에서 받은 데이터 유효성(Validation)검사
  • 어떤 Data Access를 선택할지 결정
  • 서버 프로그램이 복잡해지면 Presentaion, Data Access계층에는 별로 할 일이 없고, 도메인 계층이 비대해지는게 가장 좋음
  • 스프링에서는 @Service 어노테이션을 사용해서 표현

3. Data Access(Persistence) 계층

  • DAO 계층
  • Database / Message Queue / 외부 API와의 통신 등 처리
  • 데이터베이스 또는 원격 서비스에서 영구 데이터를 관리하는 방법을 분류하는 데이터 접근 계층
  • 데이터 소스와의 소통을 해주는 계층
  • 스프링에서는 @Repository 어노테이션을 사용해서 표현

@Controller - 사용자와의 상호 작용 처리
@Service - Data Access를 선택할지 결정
@Repository - 데이터 소스와 연결

:: 스프링 사용 이유

  • 단순 반복 작업 부분이 많았던 Controller와 Repository 쪽을 개발 관점에서 매우 쉽고 편하게 처리
  • 가장 중요한 핵심 비즈니스 로직인 Service 레이어에 더 집중 가능

:: DataBase

  • 데이터의 집합

:: DBMS (Database Management System)

  • Database 관리하고 운영하는 소프트웨어

:: RDBMS (Relational Database Management System)

  • 관계형 데이터베이스
  • 테이블 이라는 최소 단위로 구성되고, 테이블은 열과 행으로 이루어짐
  • MYSQL, PostgreSQL, Oracle Database …

H2

  • In-memory DB : 서버 작동하는 동안에만 내용 저장, 작동 멈추면 데이터 모두 삭제
  • 연습용으로 사용

MYSQL

  • 서비스 배포시 사용
  • AWS RDS 서비스를 사용해 붙여볼 것
  • 스프링과 궁합 개좋음

:: SQL (Structured Query Language)

  • RDBMS에서 사용

:: DDL (Data Definition Language)

테이블이나 관계의 구조를 생성하는데 사용

  • CREATE : 새로운 데이터베이스 및 테이블 생성
  • ALTER : 데이터베이스와 테이블의 내용 수정
  • DROP : 데이터베이스와 테이블 삭제, 데이터 및 테이블 전체 삭제
  • TRUNCATE : 데이터베이스와 테이블 삭제, 최초 테이블 만들어져 있던 상태 (컬럼값)만 남김

:: DCL (Data Control Language)

데이터 사용권한 관리하는데 사용

  • GRANT : 사용자 or ROLE에 대해 권한 부여
  • REVOKE : 사용자 or ROLE에 부여한 권한 회수

:: DML (Data Manipulation Language)

테이블에 데이터를 검색, 삽입, 수정, 삭제하는데 사용

  • INSERT : 테이블에 새로운 row 추가
  • SELECT : 테이블 row 선택
  • UPDATE : 테이블 row의 내용 수정
  • DELETE : 테이블 row 삭제

:: CREATE 제약조건

:: AUTO-INCREMENT

  • 컬럼 값 중복되지 않게 1씩 자동 증가해 고유번호 생성해줌
CREATE TABLE 테이블이름
(
    필드이름 필드타입 AUTO_INCREMENT,
    // id bigint AUTO_INCREMENT,
    ...
);

:: NOT NULL

  • 해당 필드 NULL 값 저장할 수 없게 됨
CREATE TABLE 테이블이름
(
    필드이름 필드타입 NOT NULL,
    ...
);

:: UNIQUE

  • 해당 필드는 서로 다른 값을 가져야만 함
CREATE TABLE 테이블이름
(
    필드이름 필드타입 UNIQUE,
    ...
);

:: PRIMARY KEY

  • 해당 필드가 NOT NULL과 UNIQUE 제약 조건의 특징 모두 가지게 됨
CREATE TABLE 테이블이름
(
    필드이름 필드타입 PRIMARY KEY,
    ...
);

PRIMARY KEY (PK, 기본키)

테이블 내에서 유일하게 존재하는 값의 조합을 설정해서 중복된 데이터가 테이블에 삽입되는 것을 방지하는 제약조건

사용이유

  1. 데이터 중복 방지
    • 기본키 설정하지 않는다면 최근 번호나 주소 등이 바뀌었다면 어느 정보가 정확한 정보인지 판단 어려움
    • 즉, 데이터의 무결성이 깨짐
  2. 데이터를 매우 빠르게 찾을 수 있게 됨
    • 기본키 설정하면 DBMS는 인덱스를 만듦
    • 인덱스는 일종의 목차
    • 예를 들어 주민등록번호 컬럼에 기본키 설정이 되어 있지 않다면
    • 주민등록번호 중복될 수 있는 것을 가정하여 5000만 row를 전부 확인해야함
    • 하지만 기본키가 설정되어 있다면 row를 전부 확인하지 않고 1개만 찾으면 바로 해당 데이터 반환

:: FOREIGN KEY

  • 하나의 테이블을 다른 테이블에 의존하게 만들며 데이터의 무결성을 보장
  • FK 가지는 테이블이 참조하는 기분 테이블의 열은 반드시 FK, UNIQUE 제약조건 설정되어 있어야 함
CREATE TABLE 테이블이름
(
    필드이름 필드타입,
    ...
		FOREIGN KEY(필드이름)
    REFERENCES 테이블이름(필드이름)
);

FOREIGN KEY (FK, 외래키)

  • 두개의 테이블을 연결하는 다리 역할
  • 중복되는 데이터를 없애고 주문 테이블에서 외래키를 사용해서 사용자 테이블에 접근해 주문을 한 사용자의 정보도 가져올 수 있게됨

:: CASCADE

  • FOREIGN KEY 로 연관된 데이터를 삭제,변경
CREATE TABLE 테이블이름
(
    필드이름 필드타입,
    ...
		FOREIGN KEY(필드이름)
    REFERENCES 테이블이름(필드이름) ON DELETE CASCADE 
														 //ON UPDATE CASCADE
);

항상 테이블에 모든 제약조건을 걸어야 하는 것은 아니고, 프로젝트의 상황에 따라 효율적인 제약 조건 적용


:: JOIN

  • 나누어진 테이블을 하나로 합치기
  • ON이라는 키워드 통해 기준이 되는 컬럼 선택하여 2개 테이블 합침
  • JOIN을 하려면 적어도 하나의 컴럼을 공유하고 있어야 함
  • 외래키 설정되어 있다면 해당 컬럼을 통해 JOIN 하면 해당 조건 충족
하지만 JOIN을 위해 외래키 설정하는 것이 무조건 좋은 것은 아님
외래키 설정하면 데이터 무결성 확인하는 추가 연산이 발생하고
또한, 무결성을 지켜야 하므로 상황에 따라 개발하는데 불편할 수 있음
SELECT * FROM 테이블명;
테이블 전체 불러오기
//
SELECT * FROM 테이블명 WHERE 필드명 =WHERE 다음 해당 필드값’;
테이블의 해당 필드값에 해당하는 전체 데이터만 불러오기
//
SELECT 필드명1, 필드명2 FROM 테이블명 WHERE 필드명 =WHERE 다음 해당 필드값’
테이블 필드값에 해당하는 필드명1, 필드명2 의 값 불러오기
//
SELECT 해당테이블별칭.필드명1, 해당테이블별칭.필드명2, 해당테이블별칭.필드명3 FROM 해당테이블명 별칭 
JOIN 붙힐테이블명 별칭 ON 해당테이블별칭.(SELECT 뒤에 썼던 필드명 중 겹치는)필드명 = 붙힐테이블별칭.( SELECT 뒤에 썼던 필드명 중 겹치는)필드명;

SELECT s.name, s.major_code, m.major_name FROM STUDENT s JOIN MAJOR m ON s.major_code = m.major_code;
SELECT s.name, s.major_code, m.major_name FROM STUDENT s, MAJOR m WHERE s.major_code = m.major_code;

해당테이블이 앞으로 오게 해서 겹치는 필드명 기준으로 필드명1,2,3 붙게끔 합치기

PK : Primary key 기본키
FK : Foreign key 외래키

CONSTRAINT manager_fk_student_code foreign key (student_code) references student(student_code)

//FK는 CONSTRAINT 이름을 ‘manager_fk_student_code’ 로
//student_code는 STUDENT 테이블을 참조하는 FK
//references는 FK와 같이 쓰이며 참조할 테이블 앞에 씀

:: 어플리케이션이 데이터베이스를 직접 다룰 때의 문제점

:: 번거로움

  1. 데이터베이스 만들기
  2. 어플리케이션에서 직접쿼리 만들기
  3. 쿼리를 jdbc api 통해 직접 실행하기
  4. 쿼리 결과로 해당 객체 직접 만들기

:: SQL 의존적이라 변경에 취약함

  1. 쿼리문 직접 수정
  2. 유체 객체에 값 넣어주는 부분도 당연히 추가

:: 객체지향 모델과 관계형 데이터베이스의 패러다임 불일치 발생

  • 패러다임 불일치의 문제, 쿼리 작업 줄여주기 위해 ORM(객체 관계 매핑) 등장

:: JPA (Java Persistence API)

자바 ORM 기술에 대한 표준 명세

  1. 쿼리 자동 생성
  2. SQL 의존성 줄여 번거로운 작업 단축
  3. 패러다임 불일치 해결
  4. 최적화를 위한 노력
  5. 방언 지원 -> 코드 변경 없음

JPA는 Entity 클래스의 필드 위에 연관관계 @을 설정해 주는 것만으로도 연관관계가 형성됨

관계코드 선언Entity예시
일대다 (1:N)@OneToMany- Order (1) : Food (N)배달 주문 1개에 음식 여러개 선택 가능
다대일 (N:1)@ManyToOneOwner (N) : Restaurant(1)음식점 주인 여러명이 하나의 음식점을 소유 가능
일대일 (1:1)@OneToOneOrder (1) : Coupon (1)배달 주문 1개 주문 시, 쿠폰 1개만 할인 적용 가능
다대다 (N:N)@ManyToManyUser (N) : Restaurant(N)고객은 음식점 여러개 찜 가능
음식점은 고객 여러명에게 찜 가능

:: entity package

Member class

package com.sparta.springjpa.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor // 기본생성자 만들어주는
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long Id;
    @Column(nullable = false)
    private String memberName;

    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Orders> oders = new ArrayList<>();

    public Member(String memberName) {
        this.memberName = memberName;
    }
}

Food class

package com.sparta.springjpa.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String foodName;
    @Column(nullable = false)
    private int price;

    @OneToMany(mappedBy = "food", fetch = FetchType.EAGER)
    private List<Orders> oders = new ArrayList<>();

    public Food(String foodName, int price) {
        this.foodName = foodName;
        this.price = price;
    }
}

Orders class

package com.sparta.springjpa.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Entity
@NoArgsConstructor
public class Orders {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "food_id")
    private Food food;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    public Orders(Food food, Member member) {
        this.food = food;
        this.member = member;
    }
}

:: repository package

MemberRepository interface

package com.sparta.springjpa.repository;

import com.sparta.springjpa.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {

    Optional<Member> findByMemberName(String memberName);
}

FoodRepository interface

package com.sparta.springjpa.repository;

import com.sparta.springjpa.entity.Food;
import org.springframework.data.jpa.repository.JpaRepository;

public interface FoodRepository extends JpaRepository<Food, Long> {
}

OrdersRepository interface

package com.sparta.springjpa.repository;

import com.sparta.springjpa.entity.Orders;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrdersRepository extends JpaRepository<Orders, Long> {
}

:: com.sparta.springjpa package

java folder 바로 아래

package com.sparta.springjpa;

import com.sparta.springjpa.entity.Food;
import com.sparta.springjpa.entity.Member;
import com.sparta.springjpa.entity.Orders;
import com.sparta.springjpa.repository.FoodRepository;
import com.sparta.springjpa.repository.MemberRepository;
import com.sparta.springjpa.repository.OrdersRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;

@Component
@RequiredArgsConstructor
public class Restaurant implements ApplicationRunner {

    private final FoodRepository foodRepository;
    private final OrdersRepository ordersRepository;
    private final MemberRepository memberRepository;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        List<Food> foods = new ArrayList<>();
        Food food1 = new Food("후라이드", 10000);
        foods.add(food1);
        Food food2 = new Food("양념치킨", 12000);
        foods.add(food2);
        Food food3 = new Food("반반치킨", 13000);
        foods.add(food3);
        Food food4 = new Food("고구마피자", 9000);
        foods.add(food4);
        Food food5 = new Food("아보카도피자", 110000);
        foods.add(food5);
        foodRepository.saveAll(foods);

        List<Member> members = new ArrayList<>();
        Member member1 = new Member("삼식이");
        members.add(member1);
        Member member2 = new Member("먹깨비");
        members.add(member2);
        memberRepository.saveAll(members);
        System.out.println("==================================================================");

        System.out.println("Member 데이터");
        List<Member> findMembers = memberRepository.findAll();
        for (Member findMember : findMembers) {
            System.out.println("findMember = " + findMember.getMemberName());
        }

		System.out.println("==================================================================");

        System.out.println("Food 데이터");
        List<Food> findFoods = foodRepository.findAll();
        for (Food findFood : findFoods) {
            System.out.println("findFood = " + findFood.getFoodName());
        }

        List<Orders> ordersList = new ArrayList<>();
        Orders orders1 = new Orders(findFoods.get(0), findMembers.get(0));
        ordersList.add(orders1);
        Orders orders2 = new Orders(findFoods.get(3), findMembers.get(1));
        ordersList.add(orders2);
        Orders orders3 = new Orders(findFoods.get(4), findMembers.get(1));
        ordersList.add(orders3);
        Orders orders4 = new Orders(findFoods.get(2), findMembers.get(0));
        ordersList.add(orders4);
        Orders orders5 = new Orders(findFoods.get(2), findMembers.get(0));
        ordersList.add(orders5);
        Orders orders6 = new Orders(findFoods.get(1), findMembers.get(1));
        ordersList.add(orders6);
        Orders orders7 = new Orders(findFoods.get(1), findMembers.get(0));
        ordersList.add(orders7);
        Orders orders8 = new Orders(findFoods.get(3), findMembers.get(1));
        ordersList.add(orders8);
        ordersRepository.saveAll(ordersList);
        System.out.println("==================================================================");
        int num = 1;

        System.out.println("Orders 데이터");
        List<Orders> orderList = ordersRepository.findAll();

        for (Orders orders : orderList) {
            System.out.println(num);
            System.out.println("주문한 사람 = " + orders.getMember().getMemberName());
            System.out.println("주문한 음식 = " + orders.getFood().getFoodName());
            num++;
        }
        System.out.println("==================================================================");
        System.out.println("삼식이 주문한 음식");
        Member samsik = memberRepository.findById(1L).orElseThrow(
                ()->new RuntimeException("없음")
        );

        num = 1;
        for (Orders orders : samsik.getOders()) {
            System.out.println(num);
            System.out.println("주문한 음식 = " + orders.getFood().getFoodName());
            System.out.println("주문한 음식 가격 = " + orders.getFood().getPrice());
            num++;
        }
        System.out.println("==================================================================");
        System.out.println("아보카도피자 주문한 사람");
        Food abocado = foodRepository.findById(5L).orElseThrow(
                ()->new RuntimeException("없음")
        );

        for (Orders order : abocado.getOders()) {
            System.out.println("주문한 사람 = " + order.getMember().getMemberName());
        }

        Member member = memberRepository.findByMemberName("삼식이").orElseThrow(
                () -> new RuntimeException("삼식이 없음")
        );

        System.out.println("member.getMemberName = " + member.getMemberName());
        System.out.println("member.getMemberName = " + member.getId());
    }
}

:: Spring Data JPA

Spring Data JPA 의 Query Methods

  • 스프링에서 JPA 를 Wrapping
  • 예상 가능하고 반복적인 코드를 Spring Data JPA 가 대신 작성
  • Repostiory 인터페이스만 작성하면, 필요한 구현은 스프링이 대신 알아서 척척!

예제


상품 Entity 선언

@Entity
public class Product extends Timestamped {
		@Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private Long userId;
    private String title;
    private String image;
    private String link;
    private int lprice;
    private int myprice;
}

상품 Repository 생성

public interface ProductRepository extends JpaRepository<Product, Long> {
}

기본 제공해 주는 기능

// 1. 상품 생성
Product product = new Product(...);
productRepository.save(product);

// 2. 상품 전체 조회
List<Product> products = productRepository.findAll();

// 3. 상품 전체 개수 조회
long count = productRepository.count();

// 4. 상품 삭제
productRepository.delete(product);

ID 외의 필드에 대한 추가 기능은 interface 만 선언, 구현은 Spring Data JPA 가 대신

public interface ProductRepository extends JpaRepository<Product, Long> {
    // (1) 회원 ID 로 등록된 상품들 조회
    List<Product> findAllByUserId(Long userId);

    // (2) 상품명이 title 인 관심상품 1개 조회
    Product findByTitle(String title);

    // (3) 상품명에 word 가 포함된 모든 상품들 조회
    List<Product> findAllByTitleContaining(String word);

    // (4) 최저가가 fromPrice ~ toPrice 인 모든 상품들을 조회
    List<Product> findAllByLpriceBetween(int fromPrice, int toPrice);
}

0개의 댓글