1. 프로젝트의 구현 목표와 준비

  • 프로젝트의 전체 구조
    1. 검색과 필터링을 적용할 수 있는 화면 구성, MyBatis 동적 쿼리를 이용해서 상황에 맞는 Todo 검색
    2. 새로운 Todo 등록 시, 문자열, boolean, LocalDate 자동 처리
    3. 목록에서 조회 화면으로 이동할 때 모든 검색, 필터링, 페이징 조건 유지하도록 구성
    4. 조회 화면에서 모든 조건을 유지한 채 수정 / 삭제 화면으로 이동
    5. 삭제 시 다시 목록 화면으로
    6. 수정시 다시 조회 화면으로, 검색, 필터링, 페이징 조건은 초기화
  • 프로젝트의 3티어 구성

 

 1) 프로젝트 준비

  • Spring 관련 라이브러리
// spring 관련 라이브러리
implementation group: 'org.springframework', name: 'spring-core', version: '5.3.20'
implementation group: 'org.springframework', name: 'spring-context', version: '5.3.20'
implementation group: 'org.springframework', name: 'spring-test', version: '5.3.20'
implementation group: 'org.springframework', name: 'spring-webmvc', version: '5.3.20'
implementation group: 'org.springframework', name: 'spring-jdbc', version: '5.3.19'
implementation group: 'org.springframework', name: 'spring-tx', version: '5.3.19'
  • MyBatis / MariaDB / HikariCP 관련 라이브러리
// MyBatis 라이브러리
implementation group: 'org.mybatis', name: 'mybatis', version: '3.5.6'
implementation group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.6'

// mariadb 라이브러리
implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.4'

// HikariCP 관련 라이브러리
implementation group: 'com.zaxxer', name: 'HikariCP', version: '5.0.1'
  • JSTL 관련 라이브러리
// JSTL 라이브러리
implementation group: 'jstl', name: 'jstl', version: '1.2'
  • DTO와 VO 변환을 위한 ModelMapper
// ModelMapper
implementation group: 'org.modelmapper', name: 'modelmapper', version: '3.0.0'
  • DTO 검증을 위한 validation 관련 라이브러리
// DTO 검증을 위한 validation 관련 라이브러리
implementation group: 'org.hibernate', name: 'hibernate-validator', version: '6.2.1.Final'

 

 2) 프로젝트의 폴더 / 패키지 구조

  • 예제 실습을 위해 작성했던 Sample 관련 파일 정리

  • 서버가 제대로 작동하는지 확인

 

  • 테이블 수정
drop table tbl_todo;

create table tbl_todo(
    tno int auto_increment primary key,
    title varchar(100) not null,
    dueDate date not null,
    writer varchar(50) not null,
    finished tinyint default 0
)

 

  • 서비스 패키지 설정: 프로젝트 내에 서비스 영역을 담당하는 service 패키지 생성

 

 3) ModelMapper 설정과 @Configuration

  • DTO → VO 또는 VO → DTO의 변환이 빈번하므로 ModelMapper를 스프링의 Bean으로 등록해서 처리
  • config 패키지 추가 > ModelMapperConfig 클래스 추가
// ModelMapperConfiguration
package org.zerock.springex.config;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// @Configuration은 해당 클래스가 스프링 Bean에 대한 설정을 하는 클래스림을 명시
@Configuration
public class ModelMapperConfig {

  // getMapper() 메서드가 ModelMapper를 반환
  // @Bean 어노테이션은 해당 메서드의 실행 결과로 반환된 객체를 스프링의 Bean으로 등록시키는 역할
  @Bean
  public ModelMapper getMapper() {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration()
        .setFieldMatchingEnabled(true)
        .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
        .setMatchingStrategy(MatchingStrategies.STRICT);

    return modelMapper;
  }
}

 

  • ModelMapperConfiguration을 스프링의 Bean으로 인식할 수 있도록 root-context.xml에 config 패키지를 추가
<!-- root-context.xml -->
<!-- ModelMapperConfiguration을 스프링의 Bean으로 인식시키기 위한 추가 -->
<context:component-scan base-package = 'org.zerock.springex.config'/>

 

 

2. 화면 디자인 - 부트스트랩 적용

 

Get started with Bootstrap

Bootstrap is a powerful, feature-packed frontend toolkit. Build anything—from prototype to production—in minutes.

getbootstrap.com

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Hello, world</title>
  <!-- Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
        rel="stylesheet"
        integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
        crossorigin="anonymous">
</head>
<body>
<h1>Hello, world!</h1>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
        crossorigin="anonymous">
</script>

</body>
</html>

 

 1) 부트스트랩의 container, row 적용

<!doctype html>

...

<body>
<div class = "container-fluid">
  <div class = "row">
    <h1>Header</h1>
  </div>
  <div class = "row content">
    <h1>Content</h1>
  </div>
  <div class = "row footer">
    <h1>Footer</h1>
  </div>
</div>

...

</body>
</html>

 

 2) Card 컴포넌트 적용하기

  • 부트스트랩 사이트의 Component > Card > Header and Footer 부분의 코드 사용

<!doctype html>

...

<body>

...

  <!-- body안에 "row content" 클래스 안에 "col" 클래스 생성 후 부트스트랩 코드 복사 -->
  <div class = "row content">
    <div class = "col">
      <div class="card">
        <div class="card-header">
          Featured
        </div>
        <div class="card-body">
          <h5 class="card-title">Special title treatment</h5>
          <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
          <a href="#" class="btn btn-primary">Go somewhere</a>
        </div>
      </div>
    </div>
  </div>
  
...

</body>
</html>

반응형이므로 화면 크기에 따라 자동으로 상자의 크기가 조절됨

 

 3) Navbar 컴포넌트 적용

  • 부트스트랩 사이트의 Component > Navbar > Nav 부분의 코드 사용

<!doctype html>

...

<body>
<div class = "container-fluid">
  <!-- body안에 "row" 클래스 안에 "col" 클래스 생성 후 부트스트랩 코드 복사 -->
  <div class = "row">
    <div class = "col">
      <nav class="navbar navbar-expand-lg bg-body-tertiary">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Navbar</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
              <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="#">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Features</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Pricing</a>
              </li>
              <li class="nav-item">
                <a class="nav-link disabled">Disabled</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    </div>
  </div>
  
  ...
  
</body>
</html>

네비게이션 바 역시 반응형

 

 4) Footer 처리

  • 가장 아래에 "row footer" 클래스에는 간단한 footer 적용
<!doctype html>

...

</head>
<body>

...

  <div class = "row footer">
    <div class = "row fixed-bottom" style = "z-index: -100">
      <footer class = "py-1 my-1">
        <p class = "text-center text-muted">Footer</p>
      </footer>
    </div>
  </div>
</div>

...

</body>
</html>

가장 아래에 Footer가 생성됨

 

 

3. MyBatis와 스프링을 이용한 영속 처리

  • MyBatis와 스프링을 연동하여 기존 JDBC보다 적은 양의 코드로 개발 가능
  • MyBatis를 이용한 개발 단계
    1. VO 선언
    2. Mapper 인터페이스의 개발
    3. XML의 개발
    4. 테스트 코드의 개발
  • 프로젝트에 domain 패키지 선언 > TodoVO 클래스 추가
// TodoVO
package org.zerock.springex.domain;

import lombok.*;
import java.time.LocalDate;

@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TodoVO {
  private Long tno;
  private String title;
  private LocalDate dueDate;
  private String writer;
  private boolean finished;
}

 

 1) TodoMapper 인터페이스와 XML

  • TodoVO는 Mapper 인터페이스의 파라미터나 리턴타입이 될 수 있기 때문에 먼저 정의하고 이를 이용해 TodoMapper 인터페이스 정의
// TodoMapper
package org.zerock.springex.mapper;

public interface TodoMapper {
  String getTime();
}

 

  • resources > mappers 폴더에 TodoMapper.xml을 선언하고 getTime()에 해당하는 내용 작성
  • XML 작성 시 (namespace 값 = 인터페이스의 이름), (메서드 이름 = <select> 태그의 id)로 설정
<!-- TodoMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTO Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace는 TodoMapper의 경로를 정확히 일치시켜야 함 -->
<mapper namespace = "org.zerock.springex.mapper.TodoMapper">
    <!-- id도 TodoMapper 내의 메서드 getTime()과 일치해야 함 -->
    <select id = "getTime" resultType = "string">
        select now()
    </select>
</mapper>

 

  • 테스트 코드로 동작 여부 확인
  • Test > java > org.zerock.springex.mapper > TodoMapperTests 클래스 생성
// TodoMapperTests
package org.zerock.springex.mapper;

import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
public class TodoMapperTests {
  @Autowired(required = false)
  private TodoMapper todoMapper;
  
  @Test
  public void testGetTime() {
    log.info(todoMapper.getTime());
  }
}

정상적으로 실행되었을 때 로그에 현재 시간이 출력됨을 확인

 

  • SQL 실행로그를 더 자세히 보기 위해 org.zerock.springex.mapper 패키지 로그는 TRACE 레벨로 기록하도록 log4j2.xml에 코드 추가
<!-- log4j2.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status = "INFO">
    <Appenders>

		...

    </Appenders>
    <Loggers>
        
        ...
        
        <logger name = "org.zerock.springex.mapper" level = "TRACE" additivity = "false">
            <appender-ref ref = "console" />
        </logger>
        
        ...
        
    </Loggers>
</Configuration>

동일한 테스트 실행 시 실해오디는 SQL이 출력됨을 확인

+ Recent posts