[코배웹] Part 2 Spring MVC/JPA/Thymeleaf 연습 (작성중)

지윤·2021년 7월 6일
0

Spring

목록 보기
2/7

책에서는 URL 설계가 Restful 방식이 아니다.
추후 React + Springboot 프로젝트를 위해서는 Restful API 설계에 대해서도 알아야 할 필요가 있다.

Restful API란?

REST의 기본 원칙을 성실히 지킨 서비스 디자인을 RESTful이라고 한다.

왜 사용하는가?

과거에는 웹 브라우저만 지원하면 되었지만, 지금의 서버는 웹, 앱 등 다양한 통신을 할 수 있어야 한다. 그래서 범용적인 사용성을 보장하는 서버 디자인이 필요해졌고 여기에 사용되는 것이 Restful API 설계이다.

그래서 Ajax 사용
클라이언트로 웹과 앱이 있을 때, 웹은 서버로부터 html을 응답 받고 앱은 json 데이터를 응답 받는다. 서버를 따로 만들 필요 없이 json 데이터를 응답해주면 된다.(@RestController)

@RestController은 Spring MVC Controlle에 @ResponseBody가 추가된 것이다.
@ResponseBody란, 메소드에 이 어노테이션이 있다면 메소드에서 리턴되는 값은 View 를 통해서 응답하지 않고 HTTP Response Body에 쓰여지게 된다. 이때 리턴되는 데이터 타입에 따라 MessageConverter에서 알맞은 형태로 변환이 이뤄진다.

REST 구성요소

  • 자원(RESOURCE) - URI
  • 행위(Verb) - HTTP METHOD
  • 표현(Representations)

REST 중심 규칙

  1. URI는 정보의 자원을 표현해야 한다.
  2. 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)으로 표현한다.

이외에 Rest API 설계방식 등 자세한 내용에 대해서는 아래 블로그 참고
RESTful API 설계 가이드
REST API 제대로 알고 사용하기


Service 계층은 왜 ServiceImpl 구현체를 만들어서 사용할까?

대부분 책의 예제를 보면 Service 계층은 항상 인터페이스 1개에 구현체도 1개인 형태를 보인다. 그럼 굳이 인터페이스를 만드는 이유가 무엇일까?

1. Service 계층의 역할이 무엇인가?

비즈니스 로직을 담당한다. Controller로 부터 데이터를 받아서 가공하고 요청을 처리한다.

2. 이 블로그에서 말하는 인터페이스를 사용하는 이유

  • 완전한 계층분리
  • 순수 자바코드로 작성되어 클래스 파일로 만들어 모듈화 가능
  • 비즈니스 로직이기 때문에 추가적인 요청이 생길 수 있는데, Service 인터페이스를 새로 구현하면 얼마든지 확장 가능하기 때문

3. Java에서 인터페이스를 사용하는 이유는?

객체지향 언어인 Java에서 인터페이스를 사용하는 목적은
크게 다형성, 유지보수 및 변경 용이, 느슨한 결합 등이 있다.

다형성(polymorphism)이란?
하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미하는데, Service 계층을 인터페이스로 구현함으로써 다형성을 이용한다고 볼 수 있다.

즉, 비즈니스 로직의 기능을 추가하거나 변경해야하는 상황이 오면 얼마든지 새로운 구현체를 만들어서 쉽게 변경하여 사용할 수 있다. 참고


JPA에서 쿼리 사용하기

사용자 정의 쿼리란?
JPA가 자동으로 생성하는 쿼리를 사용하는게 아닌 사용자가 정의한 대로 쿼리가 생성 혹은 데이터베이스에 종속적인 Native Query 가 생성 되는 것

JPA에서 사용자 정의 쿼리를 사용하는 방법
1. Named Query
2. 쿼리 메서드 - 메서드의 이름만으로 필요한 쿼리 생성
3. @Query - 단순한 몇 가지의 검색조건을 만들 때 사용하며, 실행할 메서드 위에 정적 쿼리를 작성, 이때 JPQL 사용
배워보자 Spring Data JPA

QueryDSL

복잡한 조합을 이용하는 경우의 수가 많은 상황에서는 동적으로 쿼리를 생성해야 한다. 이때 사용하는 것이 QueryDSL이다.
즉, SQL, JPQL을 자바 코드로 작성할 수 있도록 해주는 빌더 API 개념이며, SQL을 문자열이 아닌 자바 코드로 작성하기 때문에 Type-safety하다. 참고

사용방법

  1. build.gradle에 아래 내용 추가
implementation 'com.querydsl:querydsl-jpa'

def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
    options.annotationProcessorPath = 
    configurations.querydsl
}
  1. compileQuerydsl 실행

  2. Q도메인 클래스 생성 확인

  3. Repository 클래스에 QuerydslPredicateExecuter 인터페이스 상속 추가

  4. 사용 예제

//        다중 항목 검색
//        조건1. 제목 혹은 내용에 특정 키워드가 있다. 
//        조건2. gno >= 0
    @Test
    public void testQuery2() {

        Pageable pageable = 
        PageRequest.of(0, 10, Sort.by("gno").descending());
// Q도메인 객체 생성
        QGuestbook qGuestbook = QGuestbook.guestbook;

        String keyword = "1";
// 조건을 담는 컨테이너
        BooleanBuilder builder = new BooleanBuilder();
// 조건 생성
        BooleanExpression exTitle = qGuestbook.title.contains(keyword);
        BooleanExpression exContent = qGuestbook.content.contains(keyword);
        BooleanExpression exAll = exTitle.or(exContent);
// 컨테이너에 조건 결합
        builder.and(exAll);
        builder.and(qGuestbook.gno.gt(0L));
//QuerydslPredicateExecutor의 findAll 함수에 
//조건과 페이지 정보 파라미터로 전달
        Page<Guestbook> result =
        guestbookRepository.findAll(builder, pageable);

        result.stream().forEach(guestbook -> {
            System.out.println(guestbook);
        });
    }

의존성 주입

p157 의존성 주입과 final 키워드 사용을 보고 뭐였지?하는 나를 위해...

생성자 주입에서 final를 사용하는 이유 참고

  • 필드 객체에 final 키워드를 사용하여 컴파일 시점에 누락된 의존성을 확인할 수 있다.
  • Lombok에서 final 변수를 위한 생성자를 대신 생성해주는 어노테이션@RequiredArgsConstructor를 제공하여 코드가 간결해짐

스프링의 핵심 기술 중 하나인 DI(Dependency Injection, 의존성 주입) 방법에는 여러가지가 있다.
1. 필드 주입
2. 수정자 주입
3. 생성자 주입
4. 일반메소드 주입

생성자 주입을 사용해야 하는 이유
1. OCP 원칙을 지키며 객체의 불변성을 확보
2. 테스트 코드의 작성이 용이
3. final 키워드를 사용할 수 있고, Lombok과의 결합을 통한 간결한 코드
4. 순환 참조 문제를 를 애플리케이션 구동(객체의 생성) 시점에 파악하여 방지할 수 있다.


함수형 Java와 람다 표현식

//Guestbook을 GuestbookDTO로 변환하는 코드
Function<Guestbook, GuestbookDTO> fn1 = (entity -> entityToDto(entity));

//동일 코드
Function<Guestbook, GuestbookDTO> fn2 = 
	new Function<Guestbook, GuestbookDTO>() {
            @Override
            public GuestbookDTO apply(Guestbook guestbook) {
                return entityToDto(guestbook);
            }
};

1개의 추상 메서드만 가지는 인터페이스를 함수형 인터페이스라고 한다. 대표적으로 Comparator 인터페이스가 있다.
위에서 사용된 Function은 함수형 인터페이스로 객체 생성 시 반드시 추상메서드 apply()를 구현해야 한다.

함수형 인터페이스는 추상메서드가 오로지 1개 이므로 굳이 메서드의 이름, 제네릭 타입 등 자세한 내용을 적어줄 필요가 없다.
그래서 함수형 인터페이스를 간단하게 축약해서 표현하기 위해 람다 표현식을 사용한다.

람다식의 기본 형태는 (parameter_list) -> { 추상메서드의 구현내용 }
다양한 사용 방법에 대해서는 여기 참고


서버사이드 랜더링

Thymeleaf 템플릿 엔진을 사용하는 html에서 Tyhmeleaf 문법 오류가 있으면 500 에러가 발생하는 이유는?

서버에서 HTML(데이터가 반영된 Template)을 랜더링 하기 때문

  • 랜더링이란? html,css,JavaScript등 개발자가 작성한 문서를 브라우저에서 그래픽 형태로 출력하는 과정을 말한다.

Thymeleaf는 서버사이드 렌더링 환경에서 사용되는 템플릿 엔진으로 DB 혹은 API에서 가져온 데이터를 미리 정의된 Template에 넣어 html을 그려서 클라이언트에 전달해주는 역할을 한다.
JSP와 달리 servlet 코드로 변환되지 않으며 컨트롤러에서 값을 받아 HTML Text를 만들어 클라이언트(웹 브라우저)에 넘겨주는 역할만 담당한다.

추가로 Thymeleaf 템플릿 엔진을 사용하는 html 파일은 수정하면 바로 화면에 갱신되지 않는 이유도 서버에서 랜더링하기 때문이다. 서버사이드 랜더링은 컴파일을 해야(서버 재시작) 적용이 된다. 그래서 Spring에서는 spring-boot-devtools라는 의존성을 사용해서 개발한다.

Thymeleaf 동작 과정 참고

  1. 클라이언트의 요청을 받는다.
  2. 필요한 데이터(DB에서 가져오거나 API에서 가져오거나)를 가져온다.
  3. 미리 정의된 Template에 해당 데이터를 적절하게 넣는다.
  4. 서버에서 HTML(데이터가 반영된 Template)을 그린다.
  5. 해당 HTML을 클라이언트에 전달한다.

BooleanBuilder와 Predicate, Cloneable 인터페이스

p203
BooleanBuilder는 querydsl 라이브러리에 있는거임?

스프링 어노테이션 정리

DTO(Data Transfer Object)

웹 애플리케이션 제작 시 HttpServletRequest, HttpServletResponse를 Service 계층으로 전달하지 않는 것을 원칙으로 하듯이 엔티티 객체는 JPA 외에서 사용하지 않아야 한다.
따라서 Service 계층에서는 DTO로 파라미터와 리턴 타입을 처리하도록 구성해야 한다.

  • 엔티티는 Setter를 최소한 만들지 않는 반면, DTO는 읽고 쓰기 모두 허용(Getter, Setter)
  • 데이터를 담는 다는 것이 엔티티와 유사하지만 순수하게 전달이 목적
  • 일회성

DAO와 Repository / DTO / VO

https://kkambi.tistory.com/30
http://egloos.zum.com/aeternum/v/1160846

profile
헬로🙋‍♀️

0개의 댓글