Weekly I Learned 3주차

2022. 10. 3. 18:29Weekly I Learned (WIL)

728x90
SMALL

[기간]

- 10월 03일 ~ 10월 08일

 

 

[Weekly I Learned ( 3주차 후기)]

멘붕.. 멘붕이요.. 갑자기 쏟아지는 학습량과 정보량 떄문에 머리가 잘 돌아가지 않는 일주일이였던 것 같다.

그만큼 많은 구현 동작을 눈으로 볼 수 있어서 재밌기도 했지만 이토록 많은 기능들을 하나하나 이해하려고 하니 앞으로가 걱정이 되는 한 주다..

 

 

+ 10월 03일 (월요일)

과제에 대한 검토를 하느라 뭔가를 학습하지는 못했다. 과제 기능 구현에 온 정신집중이 되었던

날이었다.

 

 

+ 10월 04일 (화요일)

Spring 을 다룰려면 구조에 대해서 잘 숙지하고 어떻게 동작하는지 파악하는 것이 중요할 것 같다.

이번에는 Spring  프로젝트라 하면 가장 중요한 세 부분을 이해하는 시간을 가졌다.

 

[동작 순서]

1. 클라이언트 요청

2. 요청사항에 따라 Controller에 알맞은 url 경로로 찾아가 Service 에 동작 요청을 보냄

3. 원하는 결과 값을 얻을 수 있도록 Service 파트에서 동작

4-1. DB 접근이 필요한 동작의 경우 Repository 에서 DB 관련된 동작 실행 후 결과값 반환

4-2. DB 접근이 필요없을 경우 바로 동작 실행 후 결과값 반환

5. 컨트롤러에서 다시 클라이언트에게 결과값 반환

 

Controller는 가장 앞단에서 클라이언트의 요청을 받아 그대로 Service에 알맞게 전달해주는 역할을 하고

 

Service 는 Controller에게서 전달받은 요청을 가지고 동작하여 실질적으로 임무를 수행하는 부분이다.

동작할 떄 위에 적은 것처럼 DB에 대한 접근 및 활용이 필요할 때는 Repository 에 요청을 보내 DB에 대한 동작을 수행하게 된다.

 

Repository는 DB에 대한 동작을 수행하는 파트이며, 이전에는 MyBatis 라는 프레임워크를 사용하여 운영했으나 코드가 먜우 길어지고 직관적이지가 않기 때문에 요즘은 Jpa 라는 프레임워크를 사용하여 매우 간단하게 사용할 수 있게 되었다.

뿐만 아니라 코드의 양도 매우 줄어들어 고생하는 부분이 줄어들게 되었다.

 

 

 

 

+ 10월 05일 (수요일)

이번 주에 학습한 내용들을 가지고 작은 미니 프로젝트를 진행하고 있다.

나는 축구를 좋아하니 내가 좋아하는 선수들을 저장해놓고 덕질을 할 수 있는 웹이다.

 

기존에 학습할 때는 spring 자체에서 제공해주는 h2 데이터 베이스를 사용했는데,

h2 는 메모리에 저장되기 때문에 프로젝트 실행 연결을 끊어버리면 초기화가 되고 용량도 가벼우며, 바로바로 DB 에 저장된 내역 및 관리를 비교적 간단하고 쉽게 할 수 있어 주로 사용하곤 했다.

 

하지만 매번 프로젝트를 재시작할 때마다 DB 데이터들이 초기화가 되어버려 일일히 다시 넣어주어야 했기 때문에 은근히 불편했다.

그래서 mysql DB 를 연결해서 사용해보기로 하였다.

mysql 은 요즘은 잘 사용하지 않았지만 이전부터 계속해서 사용해왔던 DB 이기에 편히 사용할 수 있고 초기화가 되지 않아 작업할 때 편할 것이라 생각했다.

앞으로의 실전 프로젝트에서도 mysql을 주로 사용할 것이라 생각한다.

 

설정은 이렇다.

 

[bundle.gradle]

runtimeOnly 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

설정을 하기에 앞서 dependency에 위와 같이 mysql 과 jpa 가 import 되어있어야 한다.

(bundle.gradle -> dependecies)

 

 

[application.properties]

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username=아이디
spring.datasource.password=비밀번호
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
logging.level.org.hibernate=info
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

1. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

- 내가 사용할 DB는 mysql이기 떄문에 om.mysql.cj.jdbc.Driver 드라이버를 사용한다.

 

2. spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&characterEncoding=UTF-8

- mysql 데이터베이스 안에 주소와 사용할 DB 이름 및 문자 인코딩 형식등을 지정한다.

 

3. spring.datasource.username=root
    spring.datasource.password=wls124578!

- 해당 DB의 사용자와 비밀번호이다. 2번과 3번의 내용을 기입하려면 우선적으로 workbench 나 기타 DB 접속 툴로 해당 계정 및 DB를 생성해놓은 후 기입해야한다.

 

4. spring.jpa.show-sql=true
    spring.jpa.database=mysql
    spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

    spring.jpa.properties.hibernate.show_sql=true
    spring.jpa.properties.hibernate.format_sql=true
    spring.jpa.properties.hibernate.use_sql_comments=true

- 나는 mysql을 사용할 것이긴 하지만 sql문은 직접적으로 사용하지않고 Jpa 를 활용하여 DB관리를 할 것이기 때문에 jpa 설정도 같이 해준다.

 

이로써 properties 쪽에 DB 연결 및 설정은 완료가 되었다.

 

연결이 잘 되었는지 확인하는 작업도 빼먹지 않고 한다.

(1) 오른쪽 사이드에 붙어 있는 Database 탭을 클릭한 뒤, 위의 + 표시의 버튼을 누른다.

 

(2) 목록이 열리면, Data Source 탭의 mysql 을 누른다.

 

(3) 열린 주소 및 포트 번호, 계정 정보를 입력하는 창이 나오고 이때까지 스스로 생성한 계정 정보와 port 번호 및 주소를 기입하여 test connection 을 눌러 제대로 연결이 되는지 확인한다.

 

(4) 정상적으로 되었다면 sql 문을 그대로 기입할  수 있는 console 이 저절로 나오고, 거기서 sql을 .테스트로 작성해보고 워크벤치 같은 곳에서 잘 반영이 되었는지 확인한다.

 

그래서 오늘은 앞으로 프로젝트를 진행하게될 때 주로 사용하게될 mysql 데이터베이스와 jpa 를 연동시켜서 어떻게 sql문처럼 사용할 수 있는지 공부했다.

 

 

 

 

+ 10월 06일 (목요일)

3주차 시험을 보는 날이다.

이번주에 주로 공부했던 내용은 spring boot 의 동작에 대한 전반적인 이해, api 의 매핑을 통한 이동 및 데이터 전달이 주요한 내용이었다.

학습한 내용을 기반으로 문제를 풀어보자.

 

[문제]

1. 회원 전체 조회

  • 조회 성공 시 result 라는 양식으로 데이터를 보여줄 것
  • 조회 실패 시 에러를 message를 알맞은 형식에 맞게 보여줄 것 (그냥 에러가 나오는 것을 그대로 출력하는 것이 아닌 가공한 양식으로 출력)

2. 특정 회원 조회

  • 조회 성공 시 result 라는 양식으로 데이터를 보여줄 것
  • 조회 실패 시 에러를 message를 알맞은 형식에 맞게 보여줄 것 (그냥 에러가 나오는 것을 그대로 출력하는 것이 아닌 가공한 양식으로 출력)

 

[회원 전체 조회]

- 전체 조회는 특별한 동작 로직을 구현할 필요없이 jpa 의 findAll() 를 사용하여 db에 있는 전체 회원데이터들을 모두 가지고오면 되는 방식으로 구현하면 된다.

 

[특정 회원 조회]

- id 값을 pathVariable 로 받아 해당 아이디 값을 가진 데이터만 찾을 수 있는 jpa 의 findById() 함수를 써서 출력하도록 한다.

 

(구현 코드는 WIL이 아닌 다른 일지에서 자세히 다루도록 한다.)

 

위의 조건들을 충족하여 결과물을 뽑는 것은 그렇게 어렵다고 보긴 힘드나 결과를 출력하는 과정에서 출력 성공 및 실패 시 나오는 출력 데이터 양식을 바꾸기 위해 CommonResponse 라는 generic 클래스를 따로 생성하여 만들어주어야 했다.

 

여기서 많이 당황했던 것 같다. generic 을 사용해본 적도 없었고, 갑자기 데이터 형식까지 구현을 해야했기 때문이다.

우선, CommonReponse 클래스는 generic 이라는 특정 형식으로 만들어서 구현을 할 필요가 있는데

이 generic 이라는 것은 공통적인 기능을 동작시키는 여러 메소드의 리턴값을 이 클래스에 넣어서 공통적으로 운영할 수가 있다. <T> 라는 다소 생소한 문자를 사용하여 구분하기도 해서 익숙하지 않아 사용할 때 많이 의심을 했었던 것 같다.

 

간단하게 예시를 들어보자면,

<Controller>

@GetMapping("/member")
public ListResponse<MemberInfoResponseDto> getMemberList() {
    return responseService.getListResponse(memberService.findAllMember());
}

<MemberService>

@Transactional
public List<MemberInfoResponseDto> findAllMember() {
    List<Member> memberlist = memberRepository.findAll();
    return memberlist.stream().map(MemberInfoResponseDto::new).collect(Collectors.toList());
}

 

<ReponseService>

public<T>ListResponse<T> getListResponse(List<T> dataList){
    ListResponse listResponse = new ListResponse();
    listResponse.result = dataList;

    return listResponse;
}

<ListReponse>

public class ListResponse<T> extends CommonResponse {
   public List<T> result;

}

 

위의 코드들을 보면 정해진 형식대로 출력값을 뽑아내는 조건이 없었다면 Controller 와 Service 이 단 두가지로만 끝낼 수 있을 것이다.

여기서 정해진 형식대로 출력을 하기위해 service 에서 뽑아진 결과값을 다시 한번 generic 으로 지정한 getListResponse 로 한번 더 감싸주고 출력한다.

List<T> dataList 라고 적힌 부분이 service 단에서 출력된 리턴값 및 해당 controller 의 리턴값이 같은 것이다.

그래서 service 단에서 출력된 List<Member> 형식의 데이터가 List<T> dataList 로 들어가게 되고 또 그안에 ListResponse 라는 클래스의 List<T> generic 타입으로 지정된 result 변수로 저장되고 출력되는 것이다.

 

 

그럼 이렇게 result 변수에 저장된 데이터들이 List 형식으로 출력이 되는 것을 볼 수 있다.

 

generic은 공통된 기능 및 리턴값을 가진 여러 메소드에 공통적으로 사용할 수 있다는 점에서 실무에서도 많이 사용한다고 하는데, 이번주에 학습했던 내용을 복기하면서 generic 도 깊게는 아니지만 어떤 기능을 수행하는지 맛보기로 약간만 학습했다.

실무 역량을 키우기 위해서 generic 에 대해 좀 더 공부해야할 것으로 보인다.

 

 

 

+ 10월 07일 (금요일)

오늘은 Spring Security 에 관련된 내용을 학습했다.

Spring Security 말 그대로 보안과 연관된 기능이다.

 

웹에서 보안이라고 한다면, 대표적으로 인증 과 인가 를 꼽을 수 있는데,  이것은 주변에 흔히 볼 수 있는 로그인 동작에서 쉽게 찾아볼 수 있다. 로그인을 하면 자신의 신분을 인증할 수 있고, 로그인을 통해 해당 계정의 역할에 따라 권한을 인가 되는 것을 볼 수 있다.

카페 회원 등급을 생각하면 쉬울 것이다.

 

계정 인증이 완료가 되면 해당 계정 정보를 가지고 연결을 고의적으로 끊거나 하지 않는 이상은 지속되어야하며, 그렇지 않을 경우 쉽게 유실되어 페이지를 나갈 때마다 다시 로그인하는 번거러운 작업이 반복될 것이다.

 

이러한 유실성을 방지하기 위해 쿠키 와 세션을 활용하여 정보를 유지시켜 놓는다.

 

[쿠키]

  • 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일이다.
  • 클라이언트인 웹 브라우저에 저장된 '쿠키' 를 확인할 수 있다.
  • Application - Storage - Cookies 에 도메인 별로 저장 확인할 수 있다.
  • 구성요소
    • Name (이름): 쿠키를 구별하는 데 사용되는 키 (중복될 수 없음)
    • Value (값): 쿠키의 값
    • Domain (도메인): 쿠키가 저장된 도메인
    • Path (경로): 쿠키가 사용되는 경로
    • Expires (만료기한): 쿠키의 만료기한 (만료기한 지나면 삭제)

 

[세션]

  • 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용한다.
  • 서버에서 클라이언트 별로 유일무이한 '세션 ID' 를 부여한 후 클라이언트 별 필요한 정보를 서버에 저장한다.
  • 서버에서 생성한 '세션 ID' 는 클라이언트의 쿠키값('세션 쿠키' 라고 부름)으로 저장되어 클라이언트 식별에 사용된다.
  • 세션 동작 방식
    1. 클라이언트가 서버에 1번 요청
    2. 서버가 세션ID 를 생성하고, 응답 헤더에 전달
    3. 클라이언트가 쿠키를 저장 ('세션쿠키')
    4. 클라이언트가 서버에 2번 요청
      • 쿠키값 (세션 ID) 포함하여 요청
    5. 서버가 세션ID 를 확인하고, 1번 요청과 같은 클라이언트임을 인지

Spring Security 는 스프링 서버에 필요한 인증 및 인가를 위해 많은 기능을 제공해 줌으로써 개발의 수고를 덜어준다.

Spring Security 에서 자체적으로 제공해주는 로그인 기능도 포함하고 있으며 css만 잘 적용할 줄 안다면 다양한 로그인 기능 구현이 가능할 것이다.

 

<WebSecurityConfig>

package sparta.project.mycollectshop.Security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true) // @Secured 어노테이션 활성화
public class WebSecurityConfig {

    @Bean
    public BCryptPasswordEncoder encodePassword() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        // h2-console 사용에 대한 허용 (CSRF, FrameOptions 무시)
        return (web) -> web.ignoring()
                .antMatchers("/h2-console/**");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 회원 관리 처리 API (POST /user/**) 에 대해 CSRF 무시
        http.csrf().disable();
                // 처음에는 기능 정상 작동을 확인하기 위해서 ignoringAntMatchers 를 사용하여 일시적으로 권한을 열어두었다.
//                .ignoringAntMatchers("/user/**");
                //.ignoringAntMatchers("/api/products/**");

        http
                .authorizeHttpRequests((authz) -> authz
                        // image 폴더를 login 없이 허용
                        .antMatchers("/images/**").permitAll()
                        // css 폴더를 login 없이 허용
                        .antMatchers("/css/**").permitAll()
                        // 회원 관리 처리 API 전부를 login 없이 허용
//                        .antMatchers("/user/**").permitAll()
                        // 어떤 요청이든 '인증'
                        .anyRequest().authenticated()
                )
                // [로그인 기능]
                .formLogin()
                // 로그인 View 제공 (GET /user/login)
                .loginPage("/user/login")
                // 로그인 처리 (POST /user/login)
                .loginProcessingUrl("/user/login")
                // 로그인 처리 후 성공 시 URL
                .defaultSuccessUrl("/")
                // 로그인 처리 후 실패 시 URL
                .failureUrl("/user/login?error")
                .permitAll()
                .and()
                // [로그아웃 기능]
                .logout()
                // 로그아웃 처리 URL
                .logoutUrl("/user/logout")
                .permitAll()
                .and()
                .exceptionHandling()
                // "접근 불가" 페이지 URL 설정
                .accessDeniedPage("/forbidden.html");

        return http.build();
    }
}

Security 를 사용하려면 위와 같은 코드가 필요한데, 주요 명령어들을 살펴보자면,

 

1. BCryptPasswordEncoder 

- 비밀번호의 암호화를 해주는 기능.

 

2. .antMatchers("/ ~~ /**"); 

- / / 기호 사이에 있는 디렉토리 및 api 에 연관된 모든 접근을 허용하겠다는 명령어

   (security를 사용하게 된다면 api 설계 및 이미지 폴더 같이 접근해야하는 작업이 생긴다면 반드시 해당 명령어를 사용하여 접근 가능하도록 만들어주어야 한다.)

 

3..formLogin()

- 로그인 기능을 가리킨다.

 

4. loginPage("/user/login")

- 정해놓은 /user/login api 주소를 로그인 페이지로 확정 짓는다.

 

5. loginProcessingUrl("/user/login")

- 로그인 처리 즉, 실질적으로 로그인이 동작되게 될 주소를 정한다.

 

6. defaultSuccessUrl("/")

- 로그인 처리 후 성공 시 URL

 

7. failureUrl("/user/login?error")

- 로그인 처리 후 실패 시 URL
                

8. permitAll()

- 위에 적용 내용들을 최종적으로 컨펌하겠다는 명령어다.

 

이밖에도 security 를 본격적으로 활용하여 보안 처리를 한번에 할 수 있도록 해줄 수 있다.

security에 대한 내용이 오늘 처음 접해서 그런지는 몰라도 매우 낯설고 어려워보이는 것이 사실이나 가장 중요한 내용 중 하나라고 생각하기에 반드시 학습해 놓아야 할 것 같다.

 

 

 

+ 10월 08일 (토요일)

JPA 에 대한 심화된 내용, 대량의 데이터에 대한 페이징 처리에 대해 학습했다.

 

[JPA]

JPA는 자바 ORM 기술에 대한 표준 명세라고 한다.

즉, 쉽게 말해서 자바에서 자체적으로 DB에 관련된 동작을 수행할 수 있도록 제공해주는 API이다.

 

이전까지는 JPA가 없는 상태에선 직접적으로 SQL문을 작성해서 구현하는 것이 대부분이었다.

MyBatis 혹은 JdbcTemplate 위주로 개발이 이루어졌다.

JPA 가 코드의 양으로나 가독성, 편의성로나 장점이 많아 거의 JPA로 개발이 이루어지는 것이 요즘 트렌드라고 볼수 있다.

 

JPA를 구현하기 위해 Hibernate라는 프레임워크를 사용한다. Hibernate가 JPA 인터페이스를 구현하고 JDBC API를 사용한다.

 

Hibernate 장점

  • Hibernate는 SQL을 직접 사용하지 않고, 메서드 호출만으로 쿼리가 수행
  • 테이블 컬럼이 변경되었을 때, 테이블과 관련된 파라미터, 결과, SQL 등을 대신 수행해준다. (Entity를 활용)
  • 설정 파일에서 JPA에게 어떤 DB를 사용하고 있는지를 알려주기만 하면 얼마든지 DB를 바꿀 수 있다.
  • NativeQuery를 지원하여 SQL 자체 쿼리도 작성할 수 있다.

Hibernate 단점

  • 메서드 호출만으로 쿼리를 수행하는 것은 직접 SQL을 작성하는 것보다는 성능상 좋지 않다.
  • 메서드 호출만으로 DB 데이터를 조작하기에는 한계가 있다. 이를 보완하기 위해 JPQL을 지원한다.
  • 공부해야 할 것이 많다.

(출처: https://livenow14.tistory.com/70 [경험의 연장선:티스토리] 참고!!)

 

# JPA 공식 문서에서 사용법 일부 발췌 (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/)

Keyword
Sample JPQL snippet
Distinct findDistinctByLastnameAndFirstname select distinct …​ where x.lastname = ?1 and x.firstname = ?2
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is, Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1

 

JPA 위의 표에서 보이는 것처럼 호출문을 커스텀해서 사용이 가능하다. 

findByLastnameAndFirstname 의 샘플의 경우 적힌대로의 내용을 포함한 동작이 수행된다.

마지막 글자와 첫 글자를 가진 데이터를 찾는 것이다.

sql문으로 해석하자면 JPQL snippet 에서 보이는 것과 같다.

 

 

[JPA 연관관계]

여러 DB 테이블을 연관하여 결과를 얻기 위해선 각 테이블마다 관계에 대한 정리가 필요하다

이를 위해 JPA 어노테이션으로 간단하게 정리를 해줄 수 있다.

 

예)

- 음식 배달 서버'를 개발한다고 가정

관계
어노테이션 선언
Entity
설명
일대다 (1:N)
@OneToMany
Order (1) : Food (N)
배달 주문 1개에 음식 여러개 선택 가능
다대일 (N:1)
@ManyToOne
Owner (N) : Restaurant(1)
음식점 주인 여러명이 하나의 음식점을 소유 가능
일대일 (1:1)
@OneToOne
Order (1) : Coupon (1)
배달 주문 1개 주문 시, 쿠폰 1개만 할인 적용 가능
다대다 (N:N)
@ManyToMany
User (N) : Restaurant(N)
고객은 음식점 여러개 찜 가능 음식점은 고객 여러명에게 찜 가능

# 중요) 항상 Enitity 본인 중심으로 관계를 생각!

 

이처럼 여러 테이블을 운용할 때 연관관계 잘 정리해두는 것이 반드시 필요하다.

 

위의 내용들을 짤막하고 알기쉽게 요약하자면 기존의 Mybatis 를 사용했을 때와는 다르게 길게 코드를 작성하거나 sql문을 직접적으로 길게 일일히 작성할 필요없이 호출기능으로 호출만으로 편하고 간단하게 결과값을 추출할 수 있다.

하지만, 아무래도 직접적인 sql문을 작성하여 동작시키는 것보단 단순히 호출시키는 것이기 때문에 원하는 결과값을 추출하는데에 있어 한계가 있을 수 있다.

 

 

[페이징]

많은 데이터들이 들어오게 된다면 그 모든 정보를 전부 한번에 보여주기란 쉽지 않고 메모리도 많이 잡아먹을 것이다..

대표적으로 게시판을 예로 들면, 많은 게시판은 모든 글을 한 번에 보여주지 않고 페이지를 나눠 쪽수별로 제공한다. 정렬 방식 또한 설정하여, 보고 싶은 정보의 우선순위를 설정할 수도 있다

이처럼 정렬 방식과 페이지의 크기, 그리고 몇 번째 페이지인지의 요청에 따라 정보를 전달해주는 것이 Pagination 이다.

 

이를 개발자가 직접 구현해서 사용할 수도 있으나, JPA에서는 이를 편하게 사용할 수 있도록 Pageable 이라는 객체를 제공한다. ‘page=3&size=10&sort=id,DESC’ 형식의 QueryParameter를 추가로 요청을 보내게 되면, 쉽게 원하는 형식의 데이터들을 얻을 수 있다. 이 예시는 id의 내림차순으로 정렬한, 1쪽 10개의 글의 구성의 3번째 페이지의 정보를 요청 보내는 것이다.

 

Pageable을 사용한 간단한 UserRespository, UserController를 만들어 보면 다음과 같다.

public interface UserRepository extends JpaRepository<Job, Long> {

  List<User> findByLastname(String lastName, Pageable pageable);
}
@Controller
public class UserController {
  @GetMapping("/users")
  public List<UserResponse> findByLastName(@RequestParam String lastName, Pageable pageable) {
    // 생략
  }    
}

위와 같은 방식으로 Pageable 객체를 인수로 넘겨줌으로써 JpaRepository로 부터 원하는 Page만큼의 User 목록을 반환받을 수 있다.

GET /users?lastName=kim&page=3&size=10&sort=id,DESC

Controller에서는 Pageable 객체를 인수로 설정한 후 위와 같은 요청이 들어오게 되면, ‘page=3&size=10&sort=id,DESC’ 해당하는 Pageable 객체를 자동으로 만들어준다.

별다른 수정 없이 Respository로 Pageable을 넘겨주면 되기 때문에, 매우 편리하게 Pagination 처리를 할 수 있다.

 

Page<User> findByLastname(String lastName, Pageable pageable);

Slice<User> findByLastname(String lastName, Pageable pageable);

List<User> findByLastname(String lastName, Pageable pageable);

JPA에서는 반환 값으로 List, Slice, Page 로 다양하게 제공하고 있다. Page 구현체의 경우에는 전체 Page의 크기를 알아야 하므로, 필요한 Page의 요청과 함께, 전체 페이지 수를 계산하는 count 쿼리가 별도로 실행된다. Slice 구현체의 경우에는 전후의 Slice가 존재하는지 여부에 대한 정보를 가지고 있다.

 

(spring.jpa.show-sql=true 옵션을 설정 파일에 추가하면 Page를 반환하는 메소드 실행 시, 실제로 count 명령어가 실행되는 것을 확인 할 수 있다.)

 

 

페이징 처리와 JPA 에 대한 활용도는 개발을 하면서 점차 많아질 것으로 보이므로 차츰차츰 익숙해질 필요가 있다.

 

 

 

<주요 학습 키워드>

[DI(Dependency Injection)]

DI(Dependency Injection)란 스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로,
객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.

DI(의존성 주입)를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.

- 첫번째 방법은 A객체가 B와 C객체를 New 생성자를 통해서 직접 생성하는 방법이고,

 

- 두번째 방법은 외부에서 생성 된 객체를 setter()를 통해 사용하는 방법이다.

  이러한 두번째 방식이 의존성 주입의 예시인데,
  A 객체에서 B, C객체를 사용(의존)할 때 A 객체에서 직접 생성 하는 것이 아니라 외부(IOC컨테이너)에서 생성된 B, C객체를 조립(주입)시켜 setter 혹은 생성자를 통해 사용하는 방식이다.

스프링에서는 객체를Bean이라고 부르며, 프로젝트가 실행될때 사용자가 Bean으로 관리하는 객체들의 생성과 소멸에 관련된 작업을 자동적으로 수행해주는데 객체가 생성되는 곳을 스프링에서는 Bean 컨테이너라고 부른다.

 

[Ioc(Inversion of Control)]

메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미한다.

객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.

 

기존에는 다음과 순서로 객체가 만들어지고 실행되었다.

  1. 객체 생성
  2. 의존성 객체 생성
    (클래스 내부에서 생성)
  3. 의존성 객체 메소드 호출

하지만, 스프링에서는 다음과 같은 순서로 객체가 만들어지고 실행된다.

  1. 객체 생성
  2. 의존성 객체 주입
    (스스로가 만드는것이 아니라 제어권을 스프링에게 위임하여 스프링이 만들어놓은 객체를 주입한다.)
  3. 의존성 객체 메소드 호출

스프링이 모든 의존성 객체를 스프링이 실행될때 다 만들어주고 필요한곳에 주입시켜줌으로써 Bean들은 싱글턴 패턴의 특징을 가지며, 제어의 흐름을 사용자가 컨트롤 하는 것이 아니라 스프링에게 맡겨 작업을 처리하게 된다.

 

(https://velog.io/@gillog/Spring-DIDependency-Injection 참조!)

 

한줄 요약 !

- DI : 객체를 직접 생성하고 사용하는 것이 아니라 외부에서 생성 후 생성된 객체를 주입시켜 바로 사용하는 의존성이다.

- Bean : 스프링에서 객체를 Bean 이라고 지칭한다.

   (그렇다면 모든 객체가 Bean 이라고 봐도 무방한걸까? 만약 그렇다면 Bean 어노테이션을 따로 사용하여 지정해주기도 하던데, 그렇다면 모든 객체에 Bean 어노테이션을 붙여야하지 않을까? 하는 개인적인 궁금점이 있다.)

- IoC : 메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미한다. 즉, 호출 제어권이 나한테 없다는 뜻이다. 단지 호출문을 작성할 뿐..

 

 

갈수록 알아야 할 것이 많아지는데... 어쩔 수 없다. 나는 이제 막 코딩을 시작한 초보자이니깐...

한번에 이토록 많은 정보를 내 머리속에 담기에는 한계가 명확하다....

성급하게 하지말고 천천히 이해하도록 해봐야겠다.

728x90
반응형
LIST

'Weekly I Learned (WIL)' 카테고리의 다른 글

Weekly I Learned 6주차  (0) 2022.10.30
Weekly I Learned 5주차  (0) 2022.10.23
Weekly I Learned 4주차  (0) 2022.10.17
Weekly I Learned 2주차  (0) 2022.09.27
Weekly I Learned 1주차  (0) 2022.09.26