Spring 입문반 - 2주차

ayboori·2023년 6월 15일
0

Spring

목록 보기
3/24

3 Layer Architecture

MVC에서 Controller을 분리할 수 있다고 한 부분이 이것!
Controller의 부담을 나눈 것

가독성, 유연성이 높아진다
협업에도 좋다

  • jdbcTemplate 객체를 통해 소통한다

구성 요소

이때 Controller / Service는 모두 bean으로 등록되어 ioc에 의해 조작된다.

Controller

  • 클라이언트와 소통한다
  • 처리를 전달하는 역할
  • 받아온 데이터도 전달함
    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        MemoService memoService = new MemoService(jdbcTemplate);
        return memoService.updateMemo(id, requestDto);
    }

Sevice

  • 요구사항을 실제로 처리한다
  • 비즈니스 로직을 수행한다
  • DB 저장 및 조회가 필요할 때는 Repository에게 요청

이전에 컨트롤러가 하려던 역할 (DB와 소통하는 역할 제외) 여기서 수행

    public MemoResponseDto createMemo(MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // DB 저장
        MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
        Memo saveMemo = memoRepository.save(memo);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(saveMemo);

        return memoResponseDto;
    }

Repository

  • DB 관리 (연결, 해제, 자원 관리)

  • DB CRUD 작업을 처리

  • 쿼리 작업 등등

    public Memo save(Memo memo) {
        // DB 저장
        KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체

        String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
        jdbcTemplate.update(con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);

                    preparedStatement.setString(1, memo.getUsername());
                    preparedStatement.setString(2, memo.getContents());
                    return preparedStatement;
                },
                keyHolder);

        // DB Insert 후 받아온 기본키 확인
        Long id = keyHolder.getKey().longValue();
        memo.setId(id);

        return memo;
    }

IoC(제어의 역전) / DI(의존성 주입)

좋은 코드 작성을 위한 Spring의 핵심 기술 중 하나이다.

IoC : 설계 원칙 (구현 시 지켜야 할 룰)
DI : 디자인 패턴 (레시피 같은 순서) / 외부에서 미리 만든 객체를 주입

좋은 코드

  • 논리가 간단해야 한다.
  • 중복을 제거하고 표현을 명확하게 한다.
  • 코드를 처음 보는 사람도 쉽게 이해하고 수정할 수 있어야 한다.
  • 의존성을 최소화해야 한다.
  • 새로운 기능을 추가 하더라도 크게 구조의 변경이 없어야 한다.

강한 결합 > 약한 결합

public class Consumer {

    void eat() {
        Chicken chicken = new Chicken();
        chicken.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat();
    }
}

class Chicken {
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

Consumer가 chicken이 아닌 걸 먹고 싶어하면 어쩌지?? > 수정할 거 많아짐

public class Consumer {

    void eat(Food food) {
        food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat(new Chicken());
        consumer.eat(new Pizza());
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

이미 만들어진 메소드를 수정할 필요 없이 사용자의 요청에 응답할 수 있다.
다형성의 원리를 사용하여 구현하면 약한 결합, 약한 의존성 실현 가능

주입

여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것

Food는 인터페이스이고, Chicken, Pizze는 Food를 구현한 클래스이다.

1) 필드에 직접 주입

public class Consumer {

   Food food;

   void eat() {
       this.food.eat();
   }

   public static void main(String[] args) {
       Consumer consumer = new Consumer();
       consumer.food = new Chicken();
   }

interface Food {
   void eat();
}

1) Food 객체 생성 (Consumer에 포함 시킴)
2) Food에 필요한 객체를 주입 받음

2) 메서드를 통한 주입

   public void setFood(Food food) {
        this.food = food;
    }

Setter을 통해 매개변수로 객체를 주입 받음

3) 생성자를 통한 주입

public class Consumer {

    Food food;

    public Consumer(Food food) {
        this.food = food;
    }

생성자를 사용하여 필요한 객체를 주입받아 사용

제어의 역전

  • 이전에는 Consumer가 직접 Food를 만들어 먹었기 때문에 새로운 Food를 만들려면 추가적인 요리준비(코드변경)가 불가피했습니다.
    • 그렇기 때문에 이때는 제어의 흐름이 Consumer → Food 였습니다.
  • 이를 해결하기 위해 만들어진 Food를 Consumer에게 전달해주는 식으로 변경함으로써 Consumer는 추가적인 요리준비(코드변경) 없이 어느 Food가 되었든지 전부 먹을 수 있게 되었습니다.
    • 결과적으로 제어의 흐름이 Food → Consumer 로 역전 되었습니다.

외부에서 제어하고, 제어를 받는 (가만히 있는) 객체를 변경한다.

강한 결합의 예시 2

한 개의 객체에서 다른 객체를 생성 (new) / 그 객체가 또 다른 객체를 생성 (new)

  • 하나의 객체를 변경하면 다른 객체들도 다 변경해야 한다

강한 결합의 해결

  1. 각 객체에 대한 객체 생성은 딱 1번만!
  2. 생성된 객체를 모든 곳에서 재사용!
  3. 생성자 주입을 사용하여 필요로하는 객체에 해당 객체 주입!

파라미터로 받아오면 된다

Class Service1 {
	private final Repository1 repitory1;

	// repository1 객체 사용
	public Service1(Repository1 repository1) {
		this.repository1 = new Repository1();
		this.repository1 = repository1;
	}
}

// 객체 생성
Service1 service1 = new Service1(repository1);


IoC Container / Bean

SpringFramework가 필요한 객체를 생성, 관리해준다.

Ioc (제어의 역전)

  • 강한 결합 상태의 프로젝트는 비효율적인 코드 구성
    • 제어의 흐름은 Controller ⇒ Service ⇒ Repository
  • 하지만 DI 즉, 의존성 주입을 통해 제어의 흐름을 역전함으로서 효율적인 코드로 변화
    - Repository ⇒ Service ⇒ Controller

IoC Container

Bean을 모아둔 컨테이너

Bean

Spring이 관리하는 객체

Bean 등록 방법

등록하려는 클래스 위에 @Component 작성

Bean으로 등록되었는지 확인하는 방법


콩이 달려있으면 Bean으로 등록됨! (귀야워..)

Bean으로 등록된 이름?

클래스 이름에서 맨 앞글자가 대문자 > 소문자로 변경된 이름으로 저장되어 있다.

Bean을 주입하는 방법 1 - @Autowired

사용할 메서드 위에 @Autowired
~ 생성자 선언이 하나일 경우에는 생략 가능하다
~ 콩을 눌러보면 어느 Bean이 주입되는 건지 배울 수 있다

~ 필드 / 생성자 / 메서드 주입 모두 가능하지만, final로 선언된 객체의 경우에는 더더욱 생성자 주입이 더 권장된다.

private 필드도 Bean으로 주입할 수 있다

주입할 필드 위에 @Autowired / 추천되진 않는다

Spring Container에 의해 관리되는 Container만 주입 가능하다

Bean을 주입하는 방법 2 - Lombok

@RequiredArgsConstructor를 클래스 위에 선언한다 (생성자 선언)

@Component
@RequiredArgsConstructor // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다.
public class MemoService {

    private final MemoRepository memoRepository;
    
//    public MemoService(MemoRepository memoRepository) {
//        this.memoRepository = memoRepository;
//    }

		...

}

Bean을 주입하는 방법 3 - 수동 주입

직접 IocConatiner에 접근하여 가져오기

@Component
public class MemoService {

		private final MemoRepository memoRepository;

    public MemoService(ApplicationContext context) {
        // 1.'Bean' 이름으로 가져오기
        MemoRepository memoRepository 
        	= (MemoRepository) context.getBean("memoRepository");

        // 2.'Bean' 클래스 형식으로 가져오기
        // MemoRepository memoRepository = context.getBean(MemoRepository.class);

        this.memoRepository = memoRepository;
    }

		...		
}

3 Layer Annotation

  • 3 Layer Annotation
    • Spring 3 Layer Annotation은 Controller, Service, Repository의 역할로 구분된 클래스들을 ‘Bean’으로 등록할 때 해당 ‘Bean’ 클래스의 역할을 명시하기위해 사용됩니다. (Component가 포함되어있는 Annotation이므로 Bean 사용 가능)
      1. @Controller, @RestController
      2. @Service
      3. @Repository

Controller은 어디에서 호출되는 걸까?
Dispatcher Servlet이 호출하는 거임! 따로 호출하지 않아도 내부적으로 호출한다.


JPA CORE

DB를 직접 다룰 때 객체를 저장하는 방법?

1) DB 테이블 만들기
2) 애플리케이션에서 SQL 작성
3) SQL을 JDBC를 사용해서 직접 실행
4) SQL 결과를 객체로 직접 만들기

SQL 의존적이라 변경에 취약하다.
SQL 수정 > Dto 객체에 값을 주입하는 부분도 변경해야 한다.

ORM = Object-Relational Mapping


객체와 DB의 관계를 직접 매핑하는 도구, SQL 작업을 줄일 수 있다.

JPA = Java Persistence API

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

  • 위를 구현한 프레임워크 중 사실상 표준이 하이버네이트이다.

애플리케이션과 JDBC 사이에서 동작되며, DB 연결 과정을 직접 개발하지 않아도 자동으로 처리해준다.

profile
프로 개발자가 되기 위해 뚜벅뚜벅.. 뚜벅초

0개의 댓글