코드로 배우는 스프링부트 웹 프로젝트 - 남가람북스
https://www.namgarambooks.co.kr/entry/17-%EC%BD%94%EB%93%9C%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%9B%B9-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8
17. 코드로 배우는 스프링 부트 웹 프로젝트
저자: 구멍가게 코딩단 출판사: 남가람북스 발행일: 2020-12-25 ISBN: 979-11-89184-07-0 가격: 34000 페이지: 644 판형: 182*232*26 [상세 페이지] [저자 소개] 지은이: 구멍가게 코딩단 프로그래밍을 좋아하지..
www.namgarambooks.co.kr
Project 세팅
책에서는 MariaDB를 사용하였지만, 나는 H2 Database를 사용했다. H2 Database를 사용하기 위해 build.gradle에 아래와 같이 의존성을 주입해 주었다.
dependencies {
runtimeOnly 'com.h2database:h2'
}
또한 해당 guestBookDemo 프로젝트와 h2 Database를 연결해주기 위해서 application.yml에 아래와 같이 추가해주었다.
아래에 있는 jpa에는 database에 작성되는 로그를 console에서 확인하기 위해서 추가해주었다.
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/guestBookDemo
username: sa
password:
jpa:
properties:
hibernate:
format_sql: true
hibernate:
ddl-auto: create #create update none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
위에 항목들을 모두 했는데도 오류가 날경우
자체적으로 프로젝트 실행시 h2 DB를 생성하지 못하는 경우로, 강제적으로 해당 DB를 h2에 생성해야한다.
h2.bat을 실행하면 인터넷에 url로 h2가 켜질텐데, 해당 항목에서 아래와 같이 작성한 후 연결을 하면 성공적으로 h2에 접속이 가능하다.
JDBC URL : jdbc:h2:~/guestBookDemo
먼저 생성을 해준다음 그 다음부터는 아래에 있는 url로 접속하면 오류없이 접속이 된다.
jdbc:h2:tcp://localhost/~/guestBookDemo
기본적으로 스프링으로 컨트롤러를 사용하기 위해서는 많은 설정이 필요하지만, 스프링 부트는 자동으로 설정되는 부분들이 많아서 간단하다.예를들어, JSON 타입의 테이터를 생성하기 위해서는 Jackson-databind와 같은 라이브러리가 필요한데, 스프링부트 프로젝트에서는 'Spring Web' 의존성 항목을 추가하면 자동으로 추가가 되므로 별도의 설정이 필요하지 않다.
* JSON은 자바스크립트의 객체 표기법으로부터 파생된 부분집합으로,
속성-값 (attribute-value)로 이루어진 값이다.
기존의 스프링은 프로젝트를 실행하기 위해서는 톰캣같은 별도의 WAS가 반드시 필요했고, 이를 배포하는 과정이 필요했지만 지금의 스프링 부트에서는 단독으로 실행가능한 웹 어플리케이션을 jar 파일의 형태로 제작하고 사용하는 것이 가능해졌다.
웹 서버 (Web Server)
클라이언트가 서버에 페이지 요청을 하면 요청을 받아 정적 컨텐츠(.html, .png, .css등)를 제공하는 서버이다. 클라이언트에서 요청이 올 때 가장 앞에서 요청에 대한 처리를 한다. 클라이언트의 요청을 기다리고 요청에 대한 데이터를 만들어서 응답하는 역할을 한다. (정적 데이터)
대표적으로 Apache, nginx가 있다.
- 동작 프로세스
사용자가 정적 컨텐츠를 요청(request)했나?
YES -> 정적 컨텐츠이면, 웹서버가 제공 => .html, .png 등 응답(response)
NO -> 정적 컨텐츠가 아니면, 웹서버에서 처리를 못함으로 WAS에게 처리를 넘긴다. => 결국 WAS가 처리해준 컨텐츠를 받은 웹서버는 응답(response)을 해준다.
WAS (Web Application Server)
동적 컨텐츠를 제공하기 위해 만들어진 애플리케이션 서버이다. (DB조회, 로직처리가 요구되는 컨텐츠), JSP,Servlet 구동 환경을 제공한다. 컨테이너, 웹컨테이너, 서블릿 컨테이너라고도 부른다.
- 동작 프로세스
1. 웹서버로부터 요청이 오면 컨테이너가 받아서 처리
2. 컨테이너는 web.xml을 참조하여 해당 서블릿에 대한 쓰레드 생성하고 httpServletRequest와 httpServletResponse 객체를 생성하여 전달한다.
3. 컨테이너는 서블릿을 호출한다.
4. 호출된 서블릿의 작업을 담당하게 된 쓰레드(2번에서 만든 쓰레드)는 doPost()또는 doGet()을 호출한다.
5. 호출된 doPost(), doGet() 메소드는 생성된 동적 페이지를 Response객체에 담아 컨테이너에 전달한다.
6. 컨테이너는 전달받은 Response객체를 HTTPResponse형태로 바꿔 웹서버에 전달하고 생성되었던 쓰레드를 종료하고 httpServletRequest, httpServletResponse 객체를 소멸시킨다.
JPA(Java Persistence API)는 Java 언어를 통해서 데이터베이스와 같은 영속 계층을 처리하고자 하는 스펙이다. JPA에 대해 정확히 알기 위해서는 ORM(Object Relational Mapping)도 알아야한다.
ORM
ORM은 객체-관계 매핑으로, 간단히 말하자면 객체지향 패러다임을 관계형 데이터베이스에 보존하는 기술이다. 즉, 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것을 말한다.
- 객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용한다.
- 객체 모델과 관계형 모델 간에 불일치가 존재한다.
- ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결한다.
ORM은 완전히 새로운 패러다임을 주장하는 것이 아니라 객체지향과 관계형 사이의 변환기법을 의미한다.
JPA
JPA는 ORM을 java 언어에 맞게 사용하는 스펙이다. 따라서 ORM이 좀 더 상위 개념이되고, JPA는 java라는 언어에 국한된 개념으로 볼 수 있다. 대표적으로 Hibernate가 있다.
JpaRepository
Spring Data JPA가 제공하는 API중 JpaRepository가 있다. 해당 API를 사용하면, CRUD, 페이징, 정렬 등의 처리가 가능하다. Repsitory <- CrudRepository <- PagingAndSortRepsitory <- JpaRepository
해당구조로 JpaRepository는 상속이 되어있다.
Interface파일로 Repository를 생성하고 extends JpaRepository<Entity 타입, Id 타입>을 상속받기만 하면 준비가 끝난다.
페이징/정렬처리
페이징 처리의 경우 DB마다 방식이 달라서 복잡했었다. 오라클의 경우에는 인라인뷰를 통해 페이징을 했고, MySQL의 경우에는 limit를 사용하여 페이징을 했다. 하지만 JPA를 사용하면 Dialect를 이용해서 페이징을 처리한다.
JPA를 사용하면 페이징이 훨씬 수월해지는데, DB마다 처리방식이 다른것을 자동으로 처리해주기 때문이다. SQL문을 사용하여 페이징하는것이 아니라, API의 객체와 메서드를 사용하여 페이징을 처리할 수 있게 되었기 때문이다.
Spring Data JPA에서 페이징 처리와 정렬은 findAll() 메소드를 사용한다.
findAll() 메소드는 JpaRepository 인터페이스의 상위인 PagingAndSortRepository의 메소드로써 파라미터로 전달되는 Pageable이라는 타입의 객체에 의해서 실행되는 쿼리를 결정하게 된다.
@Test
public void testPageDefault() {
Pageable pageable = PageRequest.of(0, 10);
Page<Memo> result = memoRepository.findAll(pageable);
System.out.println(result);
}
Spring Data JPA를 이용하여 페이징할 때는 페이징 처리할 때, PageRequest()의 page를 반드시 0부터 시작해야한다. PageRequest.of(0, 10) --> 1페이지당 10개씩 출력하겠다는 의미를 전달한다.
위와 같이 Test파일에 작성하고 실행하면,
Hibernate:
select
memo0_.mno as mno1_0_,
memo0_.memoText as memotext2_0_
from
Memo memo0_ limit ?
Hibernate:
select
count(memo0_.mno) as col_0_0_
from
Memo memo0_
Page 1 of 10 containing com.example.guestbookdemo.entity.Memo instances
from절에 limit를 통해 페이징처리를 한것을 확인할 수 있다. 또한 count로 전체 데이터의 개수를 가져오는 쿼리또한 실행하는데, Page타입을 사용하면 단순히 해당 목록만을 가져오는데 그치는것이 아니라 실제 페이지 처리에 필요한 전체 데이터의 개수를 가져오기 때문에 하단 count도 실행이 된다.
Spring Data JPA에서 제공하는 Query처리 방법들
- 쿼리 메소드 : 메서드의 이름 자체가 쿼리의 구문으로 처리되는 기능
- @Query : SQL과 유사하게 엔티티 클래스의 정보를 이용해서 쿼리를 작성하는 기능
- Querydsl 등의 동적 쿼리 처리 기능
Query 메소드
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
Spring Data JPA - Reference Documentation
Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del
docs.spring.io
쿼리메소드는 주로 findBy, getBy 등으로 시작하고, And, Or 등을 추가하여 메소드의 이름 자체로 질의 조건을 만든다. 추가적인 작성 레퍼런스는 위에서 참고하면된다.
예를들어, Memo 객체에서 mno 값이 10~20 사이, 해당 값들을 역순으로 정렬하고 싶다면, 아래와 같이 작성하면 된다.
List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
@Query
쿼리메소드의 경우에는 간단한 검색같은 기능에는 사용할 수 있지만, 조건이 더 들어가게 되고 복잡한 쿼리의 경우에는 적용할 수 없다. 때문에 @Query 어노테이션을 통해 쿼리를 작성한다. @Query에서는 아래의 기능들을 제공한다.
- 필요한 데이터만 선별적으로 추출하는 기능이 가능
- 데이터베이스에 맞는 Native SQL을 사용하는 기능
- DML 등을 처리하는 기능
예를들어 Memo객체를 mno의 역순으로 정렬하고 싶다면, 아래와 같이 작성하면 된다.
@Query("select m from Memo m order by m.mno desc")
List<Memo> getListDesc();
참고
http://www.tcpschool.com/json/json_basic_structure
https://hoon-k.tistory.com/5
https://gmlwjd9405.github.io/2019/02/01/orm.html