[Spring] 스프링 예제 PetClinic 프로젝트 분석 및 기능 변경

2021. 4. 21. 01:12 Spring Framework/Spring Core

[Spring] 스프링 예제 PetClinic 프로젝트 분석 및 기능 변경

1. 프로젝트 로그 레벨 변경

로그를 통해 프로젝트의 실행 흐름을 살펴볼 수 있다.

 

PetClinic 프로젝트는 기본 로그 레벨이 INFO로 되어있어서 자세한 로그를 확인할 수 없다.

어플리케이션에서 이것저것 눌러봐도 딱히 출력되는 로그가 없다.

로그 레벨을 DEBUG로 변경하자.

Spring boot 프로젝트는 src/main/resources/application.properties 파일에 로그 레벨을 설정한다.

 

application.properties

# Logging
#logging.level.org.springframework=INFO
logging.level.org.springframework.web=DEBUG

 

기존에는 logging.level.org.springframework=INFO로 설정돼있고 그 아래 logging.level.org.springframework.web=DEBUG는 주석처리가 되어있다.

로그 레벨을 DEBUG로 바꾸기 위해 위와 같이 ...INFO를 주석처리하고 ...DEBUG의 주석처리를 풀어준다.

그리고 PetClinic을 재실행하면,

 

이제 자세한 DEBUG 로그가 출력되기 시작한다.

 

2. 프로젝트 흐름 분석

프로젝트를 실행하면 DispatcherServlet이 초기화된다.

 

FIND OWNERS를 클릭해보자.

요청 URL이 /owners/find로 전송된다.

 

로그를 보면 DispatcherServlet이 해당 GET "/owners/find" 요청을 받아 담당 컨트롤러인 OwnerController를 호출한다.

 

OwnerController의 initFindForm()에 @GetMapping 어노테이션으로 "/owners/find"이 지정되어 있다.

"/owners/find" URL로 GET 요청이 오면 이 메소드와 매핑한다는 의미이다.

initFindForm()은 owners/findOwners 뷰를 리턴한다.

 

이 뷰 파일은 프로젝트 resources/templates/owners/findOwners.html에 있다.

 

findOwners 뷰가 이 화면에 해당한다.

다른 기능도 이와 같이 분석하면 된다.

 

3. 기능 변경하기

1) FIND OWNERS 메뉴에서 Last name을 First name으로 변경하기

PetClinic 프로젝트에는 이미 owner들의 데이터가 등록되어 있다.

 

검색을 요청해서 요청 URL과 로그를 확인해보자.

Jean Coleman이라는 이름의 owner가 존재하지만 현재는 Last name을 검색하므로 결과가 나오지 않는다.

 

로그를 통해 owner 검색 요청 시 OwnerController의 processFindForm()이 처리한다는 것을 알 수 있다.

 

해당 메소드의 내용은 다음과 같다.

@GetMapping("/owners")
public String processFindForm(Owner owner, BindingResult result, Map<String, Object> model) {
 
    // allow parameterless GET request for /owners to return all records
    if (owner.getLastName() == null) {
        owner.setLastName(""); // empty string signifies broadest possible search
    }
 
    // find owners by last name
    Collection<Owner> results = this.owners.findByLastName(owner.getLastName());
    if (results.isEmpty()) {
        // no owners found
        result.rejectValue("lastName", "notFound", "not found");
        return "owners/findOwners";
    }
    else if (results.size() == 1) {
        // 1 owner found
        owner = results.iterator().next();
        return "redirect:/owners/" + owner.getId();
    }
    else {
        // multiple owners found
        model.put("selections", results);
        return "owners/ownersList";
    }
}

 

firstName에 대해 검색을 수행하도록 아래와 같이 변경해준다.

Owner 클래스에 firstName의 getter와 setter는 이미 있으므로 추가하지 않아도 되지만

findByFirstName() 메소드는 없어서 추가로 정의해야한다.

 

findByLastName()이 정의되어있는 OwnerRepository 인터페이스에 아래와 같이 findByFirstName()을 새로 정의한다.

여기서 @Param으로 정의한 파라미터 firstName을 위의 @Query에서 참조할 수 있다.

 

마지막으로 뷰에서 기존 파라미터명 lastName을 firstName으로 변경한다.

 

이제 어플리케이션을 재실행해서 결과를 확인해보자.

 

First name으로 owner를 검색해보자.

 

First name으로 검색한 결과

 

2) 부분 일치 검색으로 변경하기

현재는 쿼리가 LIKE :firstName%과 같이 되어있어 문자열의 앞부분은 완전히 일치해야 검색 결과가 나온다.

 

간단히 쿼리를 변경해서 해당 검색 기능을 변경할 수 있다.

Owner 검색 쿼리가 정의되어 있는 OwnerRepository 인터페이스로 이동한다.

 

findByFirstName() 메소드의 쿼리를 위와 같이 변경한다.

 

이제 어플리케이션을 재실행하고 다시 first name의 일부 문자열로 검색을 요청해보자.

 

'ea'으로 검색한 결과

 

3) Add Owner 기능에 Age 필드 추가하기

현재 add owner의 필드는 위와 같다.

여기에 Age를 추가해보자.

 

먼저 뷰의 form에 Age를 입력하는 input 태그를 추가한다.

 

Owner 클래스에 인스턴스 변수 age와 getter, setter를 추가한다.

 

resources/db/hsqldb/schema.sql

CREATE TABLE owners (
  id         INTEGER IDENTITY PRIMARY KEY,
  first_name VARCHAR(30),
  last_name  VARCHAR_IGNORECASE(30),
 age        INTEGER NOT NULL,
  address    VARCHAR(255),
  city       VARCHAR(80),
  telephone  VARCHAR(20)
);

 

owners 테이블에 age 컬럼을 추가한다.

 

참고로 어떤 스키마 파일을 참조하는지는 resources/application.properties 파일에서 확인할 수 있다.

 

resources/db/hsqldb/data.sql

INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (1, 'George', 'Franklin', 30, '110 W. Liberty St.', 'Madison', '6085551023');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (2, 'Betty', 'Davis', 41, '638 Cardinal Ave.', 'Sun Prairie', '6085551749');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (3, 'Eduardo', 'Rodriquez', 21, '2693 Commerce St.', 'McFarland', '6085558763');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (4, 'Harold', 'Davis', 32, '563 Friendly St.', 'Windsor', '6085553198');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (5, 'Peter', 'McTavish', 55, '2387 S. Fair Way', 'Madison', '6085552765');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (6, 'Jean', 'Coleman', 74, '105 N. Lake St.', 'Monona', '6085552654');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (7, 'Jeff', 'Black', 19, '1450 Oak Blvd.', 'Monona', '6085555387');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (8, 'Maria', 'Escobito', 29, '345 Maple St.', 'Madison', '6085557683');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (9, 'David', 'Schroeder', 67, '2749 Blackhawk Trail', 'Madison', '6085559435');
INSERT INTO owners(id, first_name, last_name, age, address, city, telephone) VALUES (10, 'Carlos', 'Estaban', 40, '2335 Independence La.', 'Waunakee', '6085555487');

 

테이블 스키마를 변경했으므로 어플리케이션이 시작될때 실행되는 INSERT문을 변경한다.

 

이제 검색 결과나 owner 목록에서 age가 추가된것을 확인할 수 있도록 관련 뷰를 변경한다.

 

resources/templates/owners/ownerDetails.html

<table class="table table-striped" th:object="${owner}">
  <tr>
    <th>Name</th>
    <td><b th:text="*{firstName + ' ' + lastName}"></b></td>
  </tr>
  <tr>
    <th>Age</th>
    <td th:text="*{age}" /></td>
  </tr>
  <tr>
    <th>Address</th>
    <td th:text="*{address}" /></td>
  </tr>
  <tr>
    <th>City</th>
    <td th:text="*{city}" /></td>
  </tr>
  <tr>
    <th>Telephone</th>
    <td th:text="*{telephone}" /></td>
  </tr>
</table>

 

resources/templates/owners/ownerList.html

<table id="owners" class="table table-striped">
    <thead>
    <tr>
        <th style="width: 150px;">Name</th>
        <th style="width: 100px;">age</th>
        <th style="width: 200px;">Address</th>
        <th>City</th>
        <th style="width: 120px">Telephone</th>
        <th>Pets</th>
    </tr>
    </thead>
    <tbody>
      <tr th:each="owner : ${selections}">
          <td>
              <a th:href="@{/owners/__${owner.id}__}" th:text="${owner.firstName + ' ' + owner.lastName}"/></a>
          </td>
          <td th:text="${owner.age}"/>
          <td th:text="${owner.address}"/>
          <td th:text="${owner.city}"/>
          <td th:text="${owner.telephone}"/>
          <td><span th:each="pet : ${owner.pets}" th:text="${pet.name} "/></td>
      </tr>
    </tbody>
</table>

 

이제 어플리케이션을 재시작해서 결과를 확인해보자.

 

💡 뷰(html)만 변경한 경우에는 재시작하지않고 build만 해도 변경 사항이 적용된다.

 

신규 owner 등록 폼에 Age가 추가되었다.

 

신규 owner 등록 결과에 Age가 함께 출력된다.

 

Owner list에 age 필드가 추가되었다.

 

References

인프런 - 백기선님의 예제로 배우는 스프링 입문(개정판)을 수강하며 정리한 포스팅입니다.

 

 

출처 : atoz-develop.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%98%88%EC%A0%9C-PetClinic-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B6%84%EC%84%9D-%EB%B0%8F-%EA%B8%B0%EB%8A%A5-%EB%B3%80%EA%B2%BD?category=869243