관리 메뉴

CASSIE'S BLOG

[비공개] 가장 빨리 만나는 스프링부트 책 정리 본문

PROGRAMMING/도서 내용 정리

[비공개] 가장 빨리 만나는 스프링부트 책 정리

ITSCASSIE1107 2024. 1. 25. 10:56

JdbcTemplate으로 리포지토리 클래스 구현하기
 
스프링 데이터 JPA를 이용한 DB접속
JPA는 다음과 같은 특징이 있다.
자바객체와 DB에 저장된 데이터를 매핑하는 기능
DB에서 실행할 CRUD처리를 캡슐화한 API
자바 객체를 검색하는데 사용하는 쿼리언어는 JPQL
 
 
JPA엔티티 클래스 작성하기
@Entity:
@Entity 에너테이션을 붙여 JPA 엔티티임을 표시합니다.
 
@Table(name = "customers") 
@Table 에너테이션을 붙여 엔티티에 대응하는 테이블 이름을 지정합니다. 기본적으로 테이블 이름을 클래스 이름과 동일하게 맞추는게 일반적이긴 합니다. (다르게 설정할 때도 있다고합니다.) 
 
@NoArgsConstructor
JPA 명세를 따르면 엔티티 클레스에는 인자를 받지않는 기본생성자를 만들어야합니다. 롬복으로 기본 생성자를 만드려면 @NoArgsConstructor 애너테이션이 있어야합니다.
 
@AllArgsConstructor
JPA와 관계없지만 쉽게 프로그래밍 할 수 있게 롬복이 기본 생성자외에 전체 필드를 인자로 받는 생성자를 만들도록 설정합니다. 
 
@Id
엔티티의 기본 키인 필드에 @Id 에너테이션을 붙입니다.
 
@GeneratedValue
DB가 키 번호를 자동으로 매기도록@GeneratedValue 에너테이션을 붙여 지정합니다.
 
 
@Column(nullable = false)
필드에 @Column 애너테이션을 붙여서 DB에 대응하는 칼럼의 이름이나 제약조건을 설정합니다. 여기서는NOTE NULL 제약조건을 설정합니다. 
 
⭐mvn dependency: tree를 실행하여 어떤 라이브러리가 추가됐는지 확인할 수 있다.
 

스프링 데이터 JPA로 리포지토리 클래스 작성하기

JpaRepository에는 다음과 같은 CRUD(CREATE, READ, UPDATE, DELETE) 조작용 기본 메서드가 정의되어있으며 JpaRepository를 상속한 인터페이스를 만드는 작업만으로 아주 쉽게 레포지토리를 작성할 수 있습니다.
-findOne
-save
-findAll
-delete
 
ex) 
package.com.example.repository;
 
import com.example.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
 
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
}
 
Q. 스프링 데이터 JPA로 리포지토리 클래스 작성하기 이 예시인데 <Customer, Integer> 이게 두 타입을 넣으면 어떤 뜻인거야?
 
스프링 데이터 JPA에서 JpaRepository는 두 개의 제네릭 타입을 가지고 있습니다. 첫 번째 제네릭 타입은 엔티티(도메인 클래스)의 타입이며, 두 번째 제네릭 타입은 엔티티의 식별자(Primary Key)의 타입입니다.
 
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
}
 

  1. Customer: 이 부분은 엔티티(도메인 클래스)의 타입입니다. Customer 클래스가 데이터베이스 테이블에 매핑되어 있고, 이 리포지토리를 통해 해당 엔티티를 조작하게 됩니다.
  2. Integer: 이 부분은 엔티티의 식별자(Primary Key)의 타입입니다. Customer 엔티티의 식별자가 Integer 타입인 것을 의미합니다. 일반적으로는 엔티티의 식별자 타입을 사용합니다.

따라서 위의 코드에서 CustomerRepository는 Customer 엔티티를 조작하며, 이 엔티티의 식별자는 Integer 타입으로 설정되어 있다는 것을 나타냅니다.
 
 

Q. 보통 JPA에서 두개의 제네릭타입을 지정해주는 것이 일반적이니?
 

일반적으로 JPA에서는 두 개의 제네릭 타입을 지정하는 것이 일반적입니다.

첫 번째 제네릭 타입은 엔티티(도메인 클래스)의 타입이며, 두 번째 제네릭 타입은 엔티티의 식별자(Primary Key)의 타입입니다. 이는 JPA가 해당 엔티티를 데이터베이스에서 어떻게 관리할지에 대한 정보를 제공합니다.

 

예를 들어, JpaRepository를 상속받는 경우, 첫 번째 제네릭 타입은 해당 리포지토리가 다룰 엔티티를 나타내고, 두 번째 제네릭 타입은 해당 엔티티의 식별자의 타입을 나타냅니다.

 

이 정보를 기반으로 Spring Data JPA는 적절한 CRUD(Create, Read, Update, Delete) 메서드를 자동으로 생성하여 제공합니다.

 

따라서 ⭐두 개의 제네릭 타입을 사용하여 엔티티와 식별자에 대한 정보를 명시 하는 것이 일반적인 JPA의 사용 방식입니다.

 
 
JPQL로 쿼리 정의하기
JpaRepository에 정의되어 있지 않은 검색 처리를 하려면 상속한 인터페이스에 해당 메서드를 추가합니다.
JPQL Java Persistence Quey Language로 쿼리를 작성할 수 있습니다.
JPQL 은 JPA로 엔티티를 조작할 때 사용하는 쿼리 언어로 SQL과 닮았습니다. JPQL은 실행될 때 SQL로 변환되며 RDBMS 기능에 따라 제각각인 SOL 언어들을 흡수합니다. 그래서 JPQL을 사용하면 특정 벤더에 의존하지 않는 쿼리를 작성할 수 있습니다.
그러나 JPQL에 관한 자세한 내용은 이 책의 범위를 벗어나므로 더는 설명하지 않겠습니다.
또 다른 예제를 살펴보겠습니다. Customer의 이름을 오름차순으로 검색하는 메서드 findAllOrderByName)를 CustomerRepository에 추가합니다.

CustomerRepository 클래스

package com. example. repository;
import com. example. domain. Customer;
import org. springframework.data. jpa. repository. JpaRepository;
import org.springframework. data. jpa. repository. Query;
import java.util.List;

public interface CustomerRepository extends JpaRepository <Customer, Integer> {
    @Query ("SELECT × FROM Customer × ORDER BY x. firstName, x. lastName")   -> 1번
    List<Customer> findAllOrderByName() ;
}
 
@Query:
JPQL을 기술할 때 @Query 애너테이션을 붙입니다.
 
 
페이징 처리 구현하기
스프링 데이터에는 데이터 접속 시 페이징 처리를 쉽게 할 수 있는 기능이 마련되어 있습니다.
JpaRepository에 페이징 처리용 메서드가 포함되어 있습니다.

🚩App 클래스
package com. example;
import com. example. domain. Customer;
import com. example. repository. CustomerRepository;
import org.springframework. beans. factory. annotation. Autowired;
import org.springframework. boot. CommandLineRunner; import org.springframework.boot. SpringApplication;
import org. springframework.boot.autoconfigure. EnableAutoConfiguration;
import org.springframework. context. annotation. ComponentScan;
import org. springframework. data. domain. Page;
import org.springframework.data. domain. PageRequest;
import org. springframework. data. domain. Pageable;

@EnableAutoConfiguration
@ComponentScan
public class App implements CommandLineRunner {
    @Autowired
    CustomerRepository customerRepository;

    @Override
    public void run(String... strings) throws Exception {
   //데이터 추가
         Customer created = customerRepository. save (new Customer(
        null, "Hidetoshi", "Dekisugi"'));
• 계속
 
 
 
        System.out.println(created + " is created!");
         //페이징 처리
         Pageable pageable = new PageRequest (0, 3);   🚩1번
          Page<Customer> page = customerRepository. findAll (pageable); 🚩2번
          System. out. printIn("한 페이지당 데이터 수=" + page. getsize()); 🚩3-1번
          System.out. print In("현재 페이지" + page. getNumber()); 🚩3-2번
          System. out. printin("전체 페이지 수=" + page. getTotalPages()); 🚩3-3번
           System.out. print in("전체 데이터 수=" + page. getTotalElements());
           page.getContent (). forEach (System. out: :println);  🚩4번
}

           public static void main(String [] args) {
           SpringApplication. run (App.class, args);
}
* 프로그램 설명
번호
① Pageable pageable = new PageRequest (0, 3);   🚩1번
Pageable 인터페이스로 페이징 정보를 가져옵니다. 본체 클래스로는 PageRequest 클래스가 있습니다. 이 PageRequest 클래스 생성자의 첫 번째 인자는 페이지 수를. 두 번째 인자는 한 페이지가 포함하는 데이터 수를 나타냅니다(페이지 수는 0부터 시작한다는 점을 주의하기 바랍니다).

② Page page = customerRepository. findAll (pageable); 🚩2번
findAll(Pageable) 메서드를 실행하여 지정한 페이지의 Customer 데이터를 가져옵니다. 반환 값은 Page 타입입니다.

③ 🚩3-1번 ~ 🚩3-3번
Page. getsize로 한 페이지에 포함되는 데이터 수를, Page.getNumber로 현재 페이지 수(0부터 시작하는 수)를, Page. getTotaLPage로 전체 페이지 수를, Page. getTotalElements로 전체 데이터 수를 가져올 수 있습니다.

page.getContent (). forEach (System. out: :println);  🚩4번

Page. getcontent로 해당 페이지의 데이터 리스트를 가져올 수 있습니다.
* 실행
실행하면 다음과 같은 내용이 출력됩니다.
 

🚩 명령 프롬프트 실행 결과

한 페이지당 데이터 수=3
현재 페이지=0
전체 페이지 수=2
전체 데이터 수=5
Customer (id=1, firstName Nobita, lastName=Nobi)
Customer (id=2, firstName=Takeshi, lastName=Goda)
Customer (id=3, firstName=Suneo, lastName Honekawa)
 
 

 
 
 

 

 

REST 웹 서비스 개발

먼저 고객 관리 시스템을 REST 웹 서비스 방식으로 만들겠습니다.
REST란 REpresentational State Transfer의 약어로, 클라이언트와 서버 간에 데이터를 주고받는 데 필요한 소프트웨어 아키텍처 스타일 중 하나입니다.

REST 웹 서비스는 서버 쪽에서 관리하고 있는 정보 중에서 클라이언트에 제공해야 할 정보를 리소스 형태로 추려냅니다. 
 
그리고 이 리소스에 CRUD 조작을 가한 후 HTTP 메서드(GET, POST, PUT, DELETE)와 연결된 웹 API를 통해 클라이언트에 제공합니다. 웹 API를 REST API라고도 부릅니다.

이번에는 고객Customer 리소스에 CRUD 조작을 가하는 기능을 갖춘 웹 API를 공개하여 HTTP를 통해 고객 정보를 다룰 수 있도록 합니다.
 
🚩페이징 처리 구현
스프링 부트를 사용하면 '2장. 스프링 프레임워크 금방 배우기'에서 소개한 스프링 데이터의 Pageable 클래스를 컨트롤러의 인자로 받아들이도록 미리 설정할 수 있습니다.
모든 고객 정보 얻기 API 코드를 조금 변경하여 한 페이지당 레코드 수를 지정할 수 있게 만듭니다.
🚩 CustomerRestController 클래스
package com.example.api;
import org.springframework. data.domain.Page;
import org.springframework.data.domain.Pageable;

@RestController
@RequestMapping ("api/customers")
        public class CustomerRestController {
         @RequestMapping (method = RequestMethod.GET)
         Page<Customer> getCustomers ( @PageableDefault Pageable pageable 🚩1번) {
         Page<Customer> customers = customerService. findAll (pageable); 🚩2번
        return customers;
     }
}
* 프로그램 설명
번호
🚩1번 Page<Customer> getCustomers ( @PageableDefault Pageable pageable 🚩1번) 
설명
Pageable 객체를 인자로 받으면 페이징 정보를 얻을 수 있습니다.
응답 파라미터에 설정한 page, size가 이 Pageable 객체에 매핑됩니다.
파라미터를 지정하지 않으면 기본값으로 설정되므로 PageableDefault 애너테이션을 붙입니다.
@PageableDefault 애너테이션에 page 나 size의 값을 지정할 수 있지만 아무것도 지정하지 않으면 기본값
page=0, size=20으로 지정됩니다. page 파라미터는 0부터 시작한다는 점을 주의합니다.
 
🚩  Page<Customer> customers = customerService. findAll (pageable); 🚩2번
설명
페이징 정보를 Customerservice에 넘겨주며 검색합니다.
결과를 Page 객체 형태로 반환하도록 합니다(findAll() 메서드는 아래에서 구현합니다).
 
 
customerservice 클래스에도 페이징 처리 메서드를 추가합니다.

🚩  CustomerService 클래스
package com. example. service;
...
import org.springframework. data. domain. Page;
import org. springframework. data. domain. Pageable;

@Service
@Transactional
public class CustomerService ‹
    public Page<Customer> findAll (Pageable pageable) {
    return customerRepository.findAllOrderByName(pageable);
   }...
}
* 실행
애플리케이션을 실행한 후 GET 요청을 보냅니다.
명령 프롬프트 모든 고객 정보 획득 AP 실행
> curl -X GET "http://localhost:8080/api/customers"
{"content": [{"id": 1, "firstName": "Nobita", "lastName": "Nobi"}, {"id" :4, "firstName": "Shiz uka", "lastName": "Minamoto"}, ("id" :3, "firstName": "Suneo", "lastName": "'Honekawa"}, {"id" :
2, "firstName": "Takeshi", "lastName": "Goda"}], "lastPage": true, "firstPage": true, "totalEl ements":4, "totalPages": 1, "last": true, "number":0, "size": 10, "sort":null,"first": true, "n umberOfElements": 4}

모든 고객 정보 얻기 API를 실행한 결과가 List=<Customer>에서 Page<Customer>로 변경되어 고객 정보는 content 속성과 페이지 정보에 관한 속성을 포함한다는 사실을 알 수 있습니다. 기본값으로 page=0, sice = 20이 설정됐기 때문에 데이터가 네 개인 경우 모든 정보가 반환됩니다.
 


Thymeleaf 를 사용해에 하면에 표시하는 웹 에플리케이션 개발 

지금까지는 화면이 없고 JSON 형식으로 포시하는 애플리케이션을 개발했습니다. 이 절에서는 여러 화면으로 구성된 웹 애플리케이션을 만들어봅니다.

Thymeleaf 는 HTML의 th: *** 속성(혹은 data-th-*** 속성)에 불어 동적인 화면을 구성할 수  있는 템플릿 엔진입니다.

브라우저나 저작 도구를 사용하여 템플릿이 제작된 모습을 그대로 확인할 수 있습니다. 디자이너 가 만들 HTML에 약간의 속성을 추가하여 서버 쪽에서 사용할 수 있고, 수정한 템플릿을 그대로 디자이너가 다시 변경할 수도 있어 디자이너를 위해 존재하는 것이라고 말할 수 있습니다. 프로그래머와 디자이너 사이에서 HTML을 변환해가며 작업을 진행할 때 불편함이 사라지므로 유용하 게 사용할 수 있는 템플릿 엔진입니다.

*
이전까지는 스프링 MVC로 웹 애플리케이션을 개발할 때 JSP JavaServer Pages를 많이 사용했습니 다. 그러나 스프링 부트에서는 Thymeleaf가 화면을 작성하기에 가장 좋습니다.
**
*
이 절에서는 지금까지 만들어온 간이 고객 정보 시스템을 Thymeleaf를 사용해 화면에 표시하겠습 니다.
다음과 같은 화면을 구성할 것입니다.
 
🚩 표 3-3 간이 고객 정보 시스템 처리 목록
 

처리 이름HTTP 메소드리소스 경로화면 이름
고객 정보 목록 표시 처리GET/customerscustomers/list

신규 고객 정보 작성 처리
POST
/customers/create
고객 정보 목록 표시 처리로 넘어감

고객 정보 편집 폼 표시 처리

GET

/customers/edit?form&id={id}
customers/edit

고객 정보 편집 처리

POST

/customers/edit&id={id]
고객 정보 목록 표시 처리로 넘어감

고객 정보 삭제 처리

POST

/customers/delete?id={id}
고객 정보 목록 표시 처리로 넘어감

 
🚩롬복의 @Data
롬복의 Data 에너테이션을 사용하면 세터/게터를 기술하지 않아도 됩니다.
 
🚩롬복의 @NotNull
입력값을 검사하기 위한 에너테이션을 붙입니다. 
firstName이라는 파라미터를 받지못하면 오류가 발생하도록 @NotNull 에너테이션을 붙입니다. 
 
🚩롬복의 @Size(min = 1, max = 127)
firstName 문자열 길이를 1~127로 제안하는 @Size 에너테이션을 붙입니다.
 
 
 
이 CustomerForm 클래스를 사용해 Custome rControLter 클래스에 create() 메서드를 만들어 서 신규 고객 정보 작성 처리를 구현합니다.
🚩CustomerController 클래스
package com. example.web;
import com.example. domain. Customer;
import com.example. service. CustomerService; import org.springframework. beans. BeanUtils;
import org.springframework. beans. factory.annotation. Autowired;
import org. springframework. stereotype. Controller;
import org.springframework.ui. Model;
import org. springframework.validation. BindingResult;
import org.springframework. validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute; import org. springframework.web.bind.annotation. RequestMapping; import org.springframework.web.bind.annotation. RequestMethod;
import java.util. List;

@Controller
@RequestMapping("customers")
public class CustomerController {
     @Autowired
     CustomerService customerService;

   @ ModelAttribute 🚩1번
     CustomerForm setUpForm() {
     return new CustomerForm() ;
}

@RequestMapping (method = RequestMethod. GET)
     String list (Model model) {
          List Customer> customers = customerService. findAl1 ();
          model. addAttribute ("customers", customers);
          return "customers/list";
}

@RequestMapping (value = "create", method = RequestMethod. POST)
     String create(@Validated 🚩2번 CustomerForm form, BindingResult result, Model model) {
     if (result. hasErrors ()) { 🚩3번 return list (model);
     }

     Customer customer = new Customer ();
     BeanUtils. copyProperties (form, customer); 🚩 4번
     customerService. create (customer);
     return "redirect: /customers"; 🚩 5번
설명
🚩1번    @ ModelAttribute 
@ModelAttribute 애너테이션을 붙인 메서드 안에서 Cus tomerForm 클래스를 초기화합니다.
@ ModelAttribute  애너테이션을 붙인 메서드는 컨트롤러 안에 있는 @RequestMapping으로 매핑된 메서드 보다 먼저 실행되며, 반환 값은 Model에 자동으로 추가됩니다. 이 예제에서는 list()나 create() 메서드가 호출되기 전에 model. addAttribute(new CustomerForm()) 부분이 실행됩니다 (addAttribute()에 서 속성 이름을 생략하면 속성 값의 클래스 이름에서 첫 문자를 소문자로 쓴 문자열이 사용됩니다. 이 예제에서는 customerForm 사용됩니다).
 
 
🚩2번  String create(@Validated 🚩2번 CustomerForm form, BindingResult result, Model model) 
폼에서 입력되어 들어온 값을 검사하기 위해 @Validated 애너테이션을 붙입니다.
그러면 CustomerForm에 설정한 Bean Validation 애너테이션이 적용되고, 그 결괏값이 옆에 있는 BindingResult 인자에 저장됩니다.
 
🚩3번  if (result. hasErrors ()) { }🚩3번
입력 검사 결과를 확인하여 오류가 있다면 다시 목록 화면을 표시합니다.
 
🚩 4번  BeanUtils. copyProperties (form, customer); 

CustomerForm을 Customer에 복사합니다.
이 예제에서는 간편하게 개발하려고 org springframework. beans. BeanUtils를 사용했지만, 필드 이름과 타입이 같을 때만 사용할 수 있습니다(더 유연한 Bean 변환을 구현하려면 DozerL ModelMapper를 이용하는 방법을 추천합니다).
 
 
🚩 5번  return "redirect: /customers"; 
신규 고객 정보 작성 처리가 제대로 끝나면 목록 화면을 표시하도록 리다이렉트합니다.
리다이렉트할 때는 뷰 이름을 redirect: 변경할 경로'로 지정합니다.
 
🚩Flyway를 이용한 DB 마이그레이션

 
 
 
이번에는 Flyway라는 DB 마이그레이션 라이브러리를 이용하겠습니다.

Flyway는 DB에 메타 정보 형태로 적용한 SOL 스크립트의 버전을 관리합니다. 새로운 버전의 SQL 스크립트를 사용하면 이전 버전과 비교하여 둘의 차이점만 DB에 적용합니다.
따라서 다음과 같은 방식으로 애플리케이션을 업데이트할 수 있습니다.
 
•  새로운 애플리케이션을 사용할 경우에는 SQL 스크립트를 모두 적용
• 이전 버전의 애플리케이션을 사용하고 있을 경우에는 SOL 스크립트의 차이점만 적용

Flyway로 DB를 마이그레이션하려면 다음 중 하나를 사용합니다.
• 명령 프롬프트 도구
• 메이븐/그레이들/앤트 플러그인
• 자바 API

스프링 부트는 애플리케이션이 시작할 때 Flyway 관련 자바 API를 자동으로 실행합니다. 특정 폴더에 SQL 스크립트를 두기만 하면 애플리케이션을 실행할 때 DB를 마이그레이션합니다.

3.3.5 CSS프레임워크 이용하기'에서 만든 애플리케이션에 Elywa>를 사용해보겠습니다.

pom.xml 파일에 다음과 같은 의존 관계를 추가하기 바랍니다.

Flyway의 버전은 spring-boot-starter-parent로 관리되므로 <version>은 지정하지 않아
도 됩니다.
" pom.xml Flyway 의존 관계 추가
<dependency>
<groupId>org.flywaydb</groupId> <artifactId>flyway-core‹/artifactId>
</dependency>
이것으로 Flyway를 사용할 준비가 끝났습니다.
***
명령 프롬프트 도구나 메이븐 플러그인으로 마이그레이션할 경우에는 공식 웹 사이트에 있는 문서를 참고하기 바랍니다.
 
@Service
클래스 위에 @Service 어노테이션을 붙이면 컴포넌트로 스캔이 됨


 
 
 

반응형