1.  Todo의 삭제 기능 개발

수정과 삭제는 GET 방식으로 조회한 후에 POST 방식으로 처리
사실상 GET 방식의 내용은 조회 화면과 같지만 스프링 MVC에는 여러 개의 경로를 배열과 같은 표기법을 이용해서 하나의 @GetMapping으로 처리할 수 있기 때문에 read() 기능을 수정해서 수정과 삭제에도 같은 메서드를 이용하도록 작업

 

TodoController의 read()를 다음과 같이 수정해서 "/todo/modify?tno=xxx"의 경로를 처리하도록 수정
 @GetMapping({"/read", "/modify"})
    public void read(Long tno, Model model) {
        // 1) request로 전달 받은 tno를 서비스에 전달해서 2)TodoDTO를 반환받아서 3)View 에 전달
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);
        model.addAttribute("dto", todoDTO);
    }

 

 

/WEB-INF/view/todo 폴더에 있는 read.jsp를 그대로 복사해서 modify.jsp를 구성


  👾  modify.jsp에서는 수정과 삭제 작업이 POST 방식으로 처리될 예정이므로 이를 위한 <form> 태그를 구성하고 수정이 가능한 항목들은 편집이 가능하도록 함

<form action="/todo/modify" method="post">
    <div class="input-group mb-3">
        <span class="input-group-text">Tno</span>
        <input type="text" name="tno" class="form-control" value="${dto.tno}" readonly>
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">Title</span>
        <input type="text" name="title" class="form-control" value="${dto.title}">
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">DueDate</span>
        <input type="date" name="dueDate" class="form-control" value="${dto.dueDate}">
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">Writer</span>
        <input type="text" name="writer" class="form-control" value="${dto.writer}" readonly>
    </div>
    <div class="form-check">
        <label class="form-check-label">
            Finished &nbsp;
        </label>
        <input type="checkbox" name="finished"
            class="form-check-input" ${dto.finished ? "checked" : ""}>
    </div>
    <div class="my-4">
        <div class="float-end">
            <button type="button" class="btn btn-danger">Remove</button>
            <button type="button" class="btn btn-primary">Modify</button>
            <button type="button" class="btn btn-secondary">List</button>
        </div>
    </div>
</form>

 

 

 

 ✓  제목 / 만료일 / 완료는 수정이 가능하도록 수정
  ✓  화면 아래 버튼은 삭제, 수정, 목록 버튼이 추가
  ✓  브라우저를 통해 "/todo/modify?tno=xx"와 같은 경로로 화면이 나오는 것 확인

 

 

 

 

 

 

 


 

1)  Remove 버튼의 처리


Remove 버튼의 클릭은 자바 스크립트를 이용해서 <form> 태그의 action을 조정하는 방식으로 동작하게 구성

<script>
    const frmModify = document.querySelector('form');
    
    document.querySelector('.btn-danger').addEventListener('click', function () {
        frmModify.action = '/todo/remove';
        frmModify.method = 'post';
        frmModify.submit();
    });
</script>

 

  ✓  Remove 버튼은 class 속성이 "btn-danger"이므로 이를 이용해서 클릭 이벤트를 처리

 

TodoController에는 POST 방식으로 동작하는 remove() 메서드를 설계
    @PostMapping("/remove")
    public String remove(Long tno, RedirectAttributes redirectAttributes) {
        log.info("-----remove----");
        log.info("tno: " + tno);

        todoService.remove(tno);
        return "redirect:/todo/list";
    }

 

  ✓  우선 tno 파라미터가 정상적으로 전달되는지 확인하고 목록으로 이동하도록 구성

  ✓  Remove 버튼을 누르면 다음과 같은 로그 출력 확인


 

2) TodoMapper와 TodoService 처리

TodoMapper에는 delete() 메서드를 추가하고 TodoMapper.xml에는 sql을 추가
public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);

    void delete(Long tno);
}
    <delete id="delete">
        DELETE FROM tbl_todo WHERE tno = #{tno}
    </delete>

 

test 코드 작성
  @Test
    public void testDelete() {
        // 1) tno로 데이터를 반환해서 정상 출력 확인 2) 삭제 3) 다시 tno로 데이터를 반환해서 삭제 확인.
        Long tno = 2L;
        TodoVO todoVO = todoMapper.selectOne(tno);
        log.info(todoVO);

        todoMapper.delete(tno);

        todoVO = todoMapper.selectOne(tno);
        log.info(todoVO);

    }

 

TodoServce / TodoServiceImpl에는 remove() 메서드를 작성
public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);

    void remove(Long tno);
}
    @Override
    public void remove(Long tno) {
        todoMapper.delete(tno);
    }

 

 

TodoController 에서 TodoService의 remove()를 호출하는 코드를 추가
    @PostMapping("/remove")
    public String remove(Long tno, RedirectAttributes redirectAttributes) {
        log.info("-----remove----");
        log.info("tno: " + tno);

        todoService.remove(tno);
        return "redirect:/todo/list";
    }

 

  ✓  브라우저를 통해서 특정한 번호를 가진 게시물이 삭제 되는지 확인

 


2.  Todo의 수정 기능 개발

Todo의 수정 기능은 수정이 가능한 항목들만 변경되어야 하므로 SQL이 복잡해짐

 

TodoMapper에 메서드 추가
public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);

    void delete(Long tno);

    void update(TodoVO todoVO);
}

 

TodoMapper.xml 코드 추가
<update id="update">
    UPDATE tbl_todo SET title = #{title}, dueDate = #{dueDate}, 
        finished = #{finished} WHERE tno = #{tno}
</update>

 

test 코드 추가
@Test
    public void testUpdate() {
        Long tno = 12L;
        TodoVO todoVO = TodoVO.builder()
                .tno(tno)
                .title("수정")
                .dueDate(LocalDate.parse("2024-4-26"))
                .finished(true)
                .build();

        todoMapper.update(todoVO);
        log.info(todoMapper.selectOne(tno));
    }

 

 

TodoService / TodoServiceImpl 에서는 TodoDTO를 TodoVO로 변환해서 처리
public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);

    void remove(Long tno);

    void modify(TodoDTO todoDTO);
}
 @Override
    public void modify(TodoDTO todoDTO) {
        log.info("...modify()...");

        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        log.info(todoVO);
        todoMapper.update(todoVO);
    }

 


1)  checkbox를 위한 Formatter


👾  수정 작업에서는 화면에서 체크박스를 이용해서 완료여부 finished (boolean) 를 처리하게 됨
👾  문제는 브라우저가 체크박스가 클릭된 상태일때 전송되는 값은 "on"이라는 값을 전달
👾  TodoDTO로 데이터를 수집할 때에는 문자열 "on"을 boolean 타입으로 처리할 수 있어야 하므로 컨트롤러에서 데이터를 수집할 때 타입을 변경해 주기 위한 CheckboxFormatter 를 formatter 패키지에 추가해서 개발

public class CheckBoxFormatter implements Formatter<Boolean> {
    @Override
    public Boolean parse(String text, Locale locale) throws ParseException {
       if (text == null) return  false;
       return text.equals("on");
    }

    @Override
    public String print(Boolean object, Locale locale) {
        return object.toString();
    }
}

 

servlet-context.xml에 format 라이브러리 추가
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <set>
            <bean class="com.example.spring_ex_01_2404.controller.formatter.LocalDateFormatter"/>
            <bean class="com.example.spring_ex_01_2404.controller.formatter.CheckBoxFormatter"/>
        </set>
    </property>
</bean>

 


2)  TodoController의 modify()

@PostMapping("/modify")
    public String midify(@Valid TodoDTO todoDTO,
                         BindingResult bindingResult,
                         RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
        // 유효성 검사 결과 에러가 있으면 수정 페이지로 돌아감
            log.info("has error");
            
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            redirectAttributes.addAttribute("tno", todoDTO.getTno());
            return "redirect:/todo/mofidy";
        }
        log.info(todoDTO);
        todoService.modify(todoDTO);
        return "redirect:/todo/list";
    }

 

  ✓  @Valid를 이용해서 필요한 내용들을 검증하고 문제가 있는 경우에는 다시 "/todo/modify"로 이동시키는 방식을 사용
  ✓  "/todo/modify"로 이동할 때는 tno 파라미터가 필요하기 때문에 RedirectAttributes의 addAttribute을 이용

 

/WEB-INF/view/todo/modify.jsp 에 검증된 정보를 처리하는 코드를 추가


  👾  <form> 태그가 끝난 후에는 <script> 태그를 이용

<script>
    const serverValidResult = {};
    <c:forEach items="${errors}" var="error">
    serverValidResult['${error.getField()}'] = '${error.defaultMessage}';
    </c:forEach>
    console.log(serverValidResult);
</script>
<script>
    const frmModify = document.querySelector('form');
                             
    document.querySelector('.btn-danger').addEventListener('click', function () {
        frmModify.action = '/todo/remove';
        frmModify.method = 'post';
        frmModify.submit();
    });
                            
    document.querySelector('.btn-primary').addEventListener('click', function () {
        frmModify.action = '/todo/modify';
        frmModify.method = 'post';
        frmModify.submit();
    });
                           
    document.querySelector('.btn-secondary').addEventListener('click', function () {
        self.location = '/todo/list';
    });
</script>

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  Todo 목록 기능 개발

등록 기능의 개발이 완료된 후 결과 화면은 목록으로 이동
목록의 경우 나중에 페이징 처리나 검색 기능이 필요하지만 시작하는 단계에서는 목록 데이터를 출력하는 수준으로 작성

 

1) TodoMapper의 개발

TodoMapper 인터페이스에 가장 최근에 등록된 TodoVO가 우선적으로 나올 수 있도록 selectAll()를 추가
public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();
}

 

 

TodoMapper.xml에서는 selectAll()의 실제 쿼리문을 작성


  👾  <select> 태그의 경우 resultType을 지정하는 것에 주의
          ➡️  resultType은 JDBC의 ResultSet의 한 행 row을 어떤 타입의 객체로 만들것인지를 지정

    <select id="selectAll" resultType="com.example.spring_ex_01_2404.domain.TodoVO">
        SELECT * FROM tbl_todo ORDER BY tno DESC
    </select>

 

마지막으로 test 폴더 내에 작성해둔 TodoMapperTests 클래스를 이용해 테스트 코드를 작성
@Test
    public void testSelectAll() {
        List<TodoVO> todoVOList = todoMapper.selectAll();
        for (TodoVO todoVO : todoVOList) {
            log.info(todoVO);
        }

        todoVOList.forEach(item -> log.info(item));
        // for문과 foreach문 둘 중 하나 사용
    }

 

  ✓  테스트 실행 결과는 가장 나중에 추가된 데이터를 우선적으로 보여줌


2) TodoService / TodoServiceImpl의 개발

서비스 계층의 개발은 특별한 파라미터가 없는 경우 TodoMapper을 호출하는것이 전부이다. 다만, TodoMapper가 반환하는 데이터의 타입이 List<TodoVO>이기 때문에 이를 List<TodoDTO>로 변환하는 작업이 필요.

 

TodoService 인터페이스에 getAll() 기능을 추가
public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();
}

 

TodoServiceImpl에서 getAll()을 작성

 

    @Override
    public List<TodoDTO> getAll() {
        List<TodoVO> voList = todoMapper.selectAll(); // dao에서 데이터베이스에서 들고온 VO리스트를 리턴
        List<TodoDTO> dtoList = new ArrayList<>();
        for (TodoVO todoVO: voList) {
            // 개별 VO를 DTO로 변환.
            TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
            dtoList.add(todoDTO); // DTO리스트에 저장.
        }
        return dtoList;
    }

 

💡  List<TodoVO>를 List<TodoDTO>로 변화하는 작업은 java8부터 지원하는 stream을 이용해서 각 TodoVO는 map()을 통해서 TodoDTO로 바꾸고, collect()를 이용해서 List로 묶어 줌

 

test 코드 작성후 실행
@Test
    public void testGetAll() {
        List<TodoDTO> todoDTOList = todoService.getAll();
        for (TodoDTO todoDTO: todoDTOList) {
            log.info(todoDTO);
        }

        todoDTOList.forEach(item -> log.info(item));
        // 둘 중 하나 사용
    }

 

 


3)  TodoController의 처리

TodoController의 list() 기능에서 TodoService를 처리하고 Model에 데이터를 담아서 JSP로 전달
@RequestMapping("/list")
    public void list(Model model) {
        log.info("todo list...");
        model.addAttribute("dtoList", todoService.getAll());
    }

 

  ✓  Model에는 dtoList라는 이름으로 목록 데이터를 담았기 때문에 JSP에서는 JSTL을 이용해서 목록을 출력

  ✓  화면 디자인은 부트스트랩의 tables 항목 참고

 

/WEB-INF/view/todo/list.jsp 페이지 생성

 

  • test.html 을 복사후 상단에 JSP, JSTL 설정 추가
  • <div class="card-body"> 부분을 아래와 같이 수정
<div class="card-body">
    <h5 class="card-title">Special title treatment</h5>
    <table class="table">
        <thead>
            <tr>
                <th scope="col">tno</th>
                <th scope="col">Title</th>
                <th scope="col">Writer</th>
                <th scope="col">DueDate</th>
                <th scope="col">Finished</th>
            </tr>
        </thead>
        <tbody>
        <c:forEach var="dto" items="${dtoList}">
            <tr>
                <th scope="row">${dto.tno}</th>
                <td>${dto.title}</td>
                <td>${dto.writer}</td>
                <td>${dto.dueDate}</td>
                <td>${dto.finished}</td>
            </tr>
        </c:forEach>
        </tbody>
    </table>
</div>

 


2.  Todo 조회 기능 개발

목록 화면에서 제목을 클릭했을때 "/todo/read?tno=xx"와 같이 TodoController를 호출하도록 개발

 

1)  TodoMapper 조회 기능 개발

 

TodoMapper의 개발은 selectOne()이라는 메소드를 추가


  👾  파라미터는 Long 타입으로 tno를 받도록 설계하고, TodoVO객체를 반환하도록 구성

public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);
}

 

TodoMapper.xml에 selectOne 추가
<select id="selectOne" resultType="com.example.spring_ex_01_2404.domain.TodoVO">
    SELECT * FROM tbl_todo WHERE tno = #{tno}
</select>

 

test 코드 작성
@Test
    public void testSelectOne() {
        TodoVO todoVO = todoMapper.selectOne(2L);
        log.info(todoVO);
    }

 


2)  TodoService / TodoServiceImpl의 개발

 

public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);
}
@Override
    public TodoDTO getOne(Long tno) {
        TodoVO todoVO = todoMapper.selectOne(tno);
        TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
        return todoDTO;
    }

 


3) TodoController의 개발


GET방식으로 동작하는 read() 기능을 개발

 @GetMapping("/read")
    public void read(Long tno, Model model) {
        // 1) request로 전달 받은 tno를 서비스에 전달해서 2)TodoDTO를 반환받아서 3)View 에 전달
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);
        model.addAttribute("dto", todoDTO);
    }

 

read.jsp 파일 생성
<div class="card-body">
    <div class="input-group mb-3">
        <span class="input-group-text">Tno</span>
        <input type="text" name="tno" class="form-control" value="${dto.tno}" readonly>
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">Title</span>
        <input type="text" name="title" class="form-control" value="${dto.title}" readonly>
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">DueDate</span>
        <input type="date" name="dueDate" class="form-control" value="${dto.dueDate}" readonly>
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">Writer</span>
        <input type="text" name="writer" class="form-control" value="${dto.writer}" readonly>
    </div>
                        
    <div class="form-check">
       <label class="form-check-label">
           Finished &nbsp;
       </label>
       <input type="checkbox" name="finished" class="form-check-input" ${dto.finished ? "checked" : ""} disabled>
    </div>
    <div class="my-4">
        <div class="float-end">
             <button type="submit" class="btn btn-primary">Modify</button>
             <button type="reset" class="btn btn-secondary">List</button>
        </div>
    </div>
</div>


4)  수정 / 삭제를 위한 링크 처리


조회 화면에 수정 / 삭제를 위해서 Modify 버튼을 클릭하면 GET 방식의 수정 / 삭제 선택이 가능한 화면으로 이동
  ➡️  자바 스크립트를 이용해서 이벤트 처리

read.jsp 의 card-body 클래스 아래에 코드 작성
<script>
    document.querySelector('.btn-primary').addEventListener('click', function () {
        self.location = '/todo/modify?tno=' + ${dto.tno};
    });
    document.querySelector('.btn-secondary').addEventListener('click', function () {
        self.location = '/todo/list';
    })
</script>

 

list.jsp의 링크 처리
<tbody>
    <c:forEach var="dto" items="${responseDTO.dtoList}">
    <tr>
        <th scope="row">${dto.tno}</th>
        <td><a href="/todo/read?tno=${dto.tno}" class="text-decoration-none">
            ${dto.title}</a></td>
        <td>${dto.writer}</td>
        <td>${dto.dueDate}</td>
        <td>${dto.finished}</td>
    </tr>
    </c:forEach>
</tbody>

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


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

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

(1) 프로젝트의 준비

build.gradle 설정 코드
    // 1. 스프링 관련
    // https://mvnrepository.com/artifact/org.springframework/spring-core
    implementation 'org.springframework:spring-core:5.3.30'
    implementation 'org.springframework:spring-context:5.3.30'
    implementation 'org.springframework:spring-test:5.3.30'
    implementation 'org.springframework:spring-webmvc:5.3.30'

    implementation 'org.springframework:spring-jdbc:5.3.30'
    implementation 'org.springframework:spring-tx:5.3.30'

 
    // 2. JSTL   
    // https://mvnrepository.com/artifact/javax.servlet/jstl
    implementation 'javax.servlet:jstl:1.2'


    // 3. MyBatis / MySQL / HikariCP 관련
    // https://mvnrepository.com/artifact/mysql/mysql-connector-java
    implementation 'mysql:mysql-connector-java:8.0.33'
    // https://mvnrepository.com/artifact/com.zaxxer/HikariCP
    implementation 'com.zaxxer:HikariCP:5.0.1'
    // https://mvnrepository.com/artifact/org.mybatis/mybatis
    implementation 'org.mybatis:mybatis:3.5.9'
    // https://mvnrepository.com/artifact/org.mybatis/mybatis-spring
    implementation 'org.mybatis:mybatis-spring:2.0.7'


    // 4. DTO와 VO의 변환을 위한 ModelMapper
    // https://mvnrepository.com/artifact/org.modelmapper/modelmapper
    implementation 'org.modelmapper:modelmapper:3.0.0'


    // 5. DTO 검증을 위한 validate 관련 라이브러리
    // https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
    implementation 'org.hibernate.validator:hibernate-validator:6.2.1.Final'

 


 

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

테이블 생성
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
)

 

패키지 구조

 


 

(3) ModelMapper 설정과 @Configuration

👩🏻‍💻  프로젝트 개발에는 DTO를 VO로 변환하거나 VO를 DTO로 변환해야 하는 작업이 빈번하므로 이를 처리하기 위해서 ModelMapper를 스프링의 빈으로 등록해서 처리

  • config패키지에  ModelMapperConfig 클래스 생성
  • ModelMapperConfig는 @Configuration을 이용
  • @Configuration은 해당 클래스가 스프링 빈에 대한 설정을 하는 클래스임을 명시
@Configuration
public class ModelMapperConfig {
    @Bean
    public ModelMapper getMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration()
                .setFieldMatchingEnabled(true)
                .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
                .setMatchingStrategy(MatchingStrategies.STRICT);
        return modelMapper;
    }
}

 

  ✓  ModelMapperConfig 클래스 내에는 getMapper()라는 메서드가 ModelMapper를 반환하도록 설계
  ✓  getMapper() 선언부에 있는 @Bean 어노테이션은 해당 메서드의 실행 결과로 반환된 객체를 스프링의 빈으로 등록시키는 역할

 

  • ModelMapperConfig를 스프링 빈으로 인식할 수 있도록 root-context.xml에 config 패키지를 component-scan을 이용해서 추가
<context:component-scan base-package="com.example.spring_ex_01_2404.config"/>

 


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

👩🏻‍💻   JSP 파일을 작성하기 전, 프로젝트의 시작 단계에서 화면 디자인을 결정하는 것이 좋음
         ✓
화면 디자인 없이 개발을 진행할 때는 나중에 코드를 다시 입혀야 하는 작업을 할 수도 있기 때문
👩🏻‍💻 
최근에는 부트스트랩 (bootstrap)이나 머터리얼(Material Design)과 같이 쉽게 웹 화면을 디자인할 수 있는 라이브러리들 덕분에 전문적인 웹 디자이너의 도움 없이도 어느정도 완성도가 있는 디자인 작업이 가능해 짐

 

https://elements.envato.com/web-templates/site-templates

 

HTML Website Templates - Envato Elements

Browse our Collection of fully customizable HTML templates. Get Unlimited Downloads with a subscription with Envato Elements.

elements.envato.com

 

📌  webapp의 resources 폴더에 test.html을 작성해서 부트스트랩을 적용하는 페이지를 작성

 

✓  부트스트랩의 화면 구성에는 container와 row를 이용


✓  Card 컴포넌트 적용하기

    부트스트랩에는 화면을 쉽게 제작할 수 있는 여러 종류의 컴포넌트를 제공
    이중에서 Card라는 컴포넌트를 적용해서 현재의 화면에서 Content라는 영역을 변경


✓  Navbar 컴포넌트의 적용
    화면 상단에는 간단한 메뉴를 보여줄 수 있는 Nav 혹은 Navbar 컴포넌트를 적용
    공식 문서의 Navbar의 예제를 선택해서 Header 라고 출력되는 부분에 적용


✓  Footer 처리
    맨 아래 <div class="row">에는 간단한 <footer>를 적용
    해당 <div>를 맨 아래쪽으로 고정하기 위해서 fixed-bottom을 적용
    내용이 많은 경우에는 Footer 영역으로 인해 가려질 수 있는 부분이 있으므로 z-index 값은 음수로 처리해서 가려질 수 있도록 구성

 

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

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

    <title>Hello, world!</title>
</head>

<body>
<div class="container-fluid">
    <div class="row">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">Navbar</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
                    <div class="navbar-nav">
                        <a class="nav-link active" aria-current="page" href="#">Home</a>
                        <a class="nav-link" href="#">Features</a>
                        <a class="nav-link" href="#">Pricing</a>
                        <a class="nav-link disabled">Disabled</a>
                    </div>
                </div>
            </div>
        </nav>
        
        <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>
    </div>
    
    <div class="row content">
        <h1>Content</h1>
    </div>
    <div class="row footer">
        <div class="row fixed-bottom" style="z-index: -100">
            <footer class="py-1 my-1">
                <p class="tab-content text-muted">Footer</p>
            </footer>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>

</body>
</html>

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  MyBatis 

스프링 프레임워크의 중요한 특징 중 하나는 다른 프레임워크들을 쉽게 결합해서 사용할 수 있다는 점이다. 이것은 스프링 프레임워크가 웹이나 데이터베이스와 같이 특정한 영역을 구애받지 않고 시스템의 객체지향 구조를 만드는데 이용된다는 성격 때문.

 

데이터베이스와 관련해서 스프링 프레임워크는 자체적으로 'spring-jdbc'와 같은 라이브러리를 이용해서구현할 수도 있고, MyBatisJPA 프레임워크를 이용하는 방식도 존재


💫  MyBatis는 'Sql Mapping Framework'라고 표현

  • 'Sql Mapping'이라는 단어가 의미하는 바는 SQL의 실행 결과를 객체지향으로 '매핑'해 준다는 뜻
  •  MyBatis를 이용하면 기존의 SQL을 그대로 사용할 수 있고 다음과 같은 점들이 편리해짐
  PreparedStatement / ResultSet의 처리 : 기존에 프로그램을 작성해서 하나씩 처리해야 하는 파라미터나 ResultSet의 getXXX()를 MyBatis가 알아서 처리해 주기 때문에 기존에 비해서 많은 양의 코드를 줄일 수 있음
  Connection / PreparedStatement / ResultSet의 close() 처리 : MyBatis와 스프링을 연동해서 사용하는 방식을 이용하면 자동으로 close() 처리할 수 있음
  SQL의 분리 : MyBatis를 이용하면 별도의 파일이나 어노테이션 등을 이용해서 SQL을 선언함. 파일을 이용하는 경우에는 SQL을 별도의 파일로 분리해서 운영이 가능  ➡️ 자바 코드에 쿼리문을 넣지 않아도 됨

 

1)  MyBatis와 스프링의 연동 방식


👻  MyBatis는 단독으로도 실행이 가능한 완전히 독립적인 프레임워크이지만, 스프링 프레임워크는 MyBatis와 연동을 쉽게 처리할 수 있는 라이브러리와 API들을 제공
👻  스프링에서 제공하는 라이브러리를 이용하는지 여부에 따라서 다음과 같은 방식 중에 하나로 개발이 가능

  • MyBatis를 단독으로 개발하고 스프링에서 DAO를 작성해서 처리하는 방식
    ➡️  기존의 DAO에서 SQL의 처리를 MyBatis를 이용하는 구조로써 완전히 MyBatis와 스프링 프레임워크를 독립적인 존재로 바라보고 개발하는 방식
  • MyBatis와 스프링을 연동하고 Mapper 인터페이스만 이용하는 방식
    ➡️  스프링과 MyBatis 사이에 'mybatis-spring'이라는 라이브러리를 이용해서 스프링이 데이터베이스 전체에 대한 처리를 하고
    MyBatis는 일부 기능 개발에 활용하는 방식. 개발 시에는 Mapper 인터페이스라는 방식을 이용해서 인터페이스만으로 모든 개발이 가능

 

2) MyBatis를 위한 라이브러리들


👻  MyBatis를 이용하려면 다음과 같은 라이브러리들이 필요

스프링 관련 : spring-jdbc, spring-tx
MyBatis 관련 : mybatis, mybatis-spring
  ➡️  다른 스프링 관련 라이브러리들과 버전이 같도록 통일해서 추가


    📌  MyBatis 관련 라이브러리는 검색을 이용해서 추가. MyBatis 버젼과 mybatis-spring 라이브러리의 버전은 일치하지 않으므로 주의해서 사용

 



3) MyBatis를 위한 스프링 설정 - SqlSessionFactory


👻  MyBatis를 이용하기 위해서는 스프링에 설정해둔 HikariDataSource를 이용해서 SqlSessionFactory라는 빈을 설정
👻  root-context.xml에 'mybatis-spring' 라이브러리에 있는 클래스를 이용해서 <bean>을 등록

 

 


2. Mapper 인터페이스 활용하기

MyBatis는 SQL 파일을 별도로 처리할 수 있지만 인터페이스와 어노테이션으로도 처리가 가능

 

📌  프로젝트에 mapper라는 이름의 패키지를 구성하고 현재 시간을 처리하는 TimeMapper 인터페이스를 선언

 


🎃  TimeMapper는 데이터베이스의 현재 시각을 문자열로 처리하도록 구성

🎃  MyBatis에는 @Select 어노테이션을 이용해서 쿼리를 작성할 수 있는데, JDBC와 마찬가지로 ";"을 이용하지 않으므로 주의

 

 

 

🎃  작성된 인터페이스를 매퍼(Mapper) 인터페이스라고 하는데 마지막으로 어떠한 매퍼 인터페이스를 설정했는지 root-context.xml에 등록해 주어야 함
🎃 root-context.xml에는 <mybatis:scan> 태그를 이용해서 매퍼 인터페이스의 설정을 추가
🎃  root-context.xml 파일 상단의 xmlns, xsi 설정에 mybatis-spring 관련 설정이 추가 되어야 함.




 

1)  테스트 코드를 통한 확인


👾  test 폴더에 mapper 패키지와 TimeMapperTests 테스트 클래스를 작성해서 확인

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;

import static org.junit.jupiter.api.Assertions.*;

@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
class TimeMapperTest {
    @Autowired(required = false)
    private TimeMapper timeMapper;

    @Test
    public void testGetTimer() {
        log.info(timeMapper.getTime());
    }

}


  📍  테스트 코드에서는 @Autowired 내에 required 속성을 지정
      ✓  @Autowired(required = false)로 지정하면 해당 객체를 주입 받지 못하도라도 예외가 발생하지 않는데 인텔리제이의 경우@Service, @Repository 와 같이 직접 스프링의 빈으로 등록된 경우가 아니라면 경고가 발생하므로 이를 방지하기 위해서 사용

 


3.  XML로 SQL 분리하기

👩🏻‍💻  MyBatis를 이용할 때 SQL은 @Select와 같은 어노테이션을 이용해서 사용하기도 하지만 대부분은 SQL을 별도의 파일로 분리하는 것을 권장
👩🏻‍💻  XML을 이용하는 이유는 SQL이 길어지면 이를 어노테이션으로 처리하기가 복잡해지고, 어노테이션이 나중에 변경되면 프로젝트 전체를 다시 빌드하는 작업이 필요하기 때문에 단순 파일로 사용하는 것이 편리

 

XML과 매퍼 인터페이스를 같이 결합할 때는 다음과 같은 과정으로 작성
  1. 매퍼 인터페이스를 정의하고 메소드를 선언
  2. 해당 XML 파일을 작성 (파일 이름과 매퍼 인터페이스 이름을 같게)하고 <select>와 같은 태그를 이용해서 SQL을 작성
  3. <select>, <insert> 등의 태그에 id 속성 값을 매퍼 인터페이스의 메소드 이름과 같게 작성



🎃  TimeMapper2 매퍼 인터페이스를 정의
🎃  어노테이션이 없는 getNow() 메서드만을 작성

 

 

 

🎃  main/resources 폴더에 mapper 폴더를 추가

 

🎃  TimeMapper2.xml을 다음과 같이 작성 (매퍼 인터페이스와 같은 이름으로 대소문자 주의)
  ➡️ 스프링 버전이 업그레이드 되면서 인터페이스와 xml의 이름을 똑같이 하지 않아도 됨

  ➡️ 하지만 관리상의 목적으로 매퍼 인터페이스와 xml의 이름을 같게 하기도 함
🎃  <mapper> 태그의 namespace 속성에서 매퍼 인터페이스를 맞게 지정하면 됨
  ➡️ <mapper> 태그의 namespace 속성을 반드시 매퍼 인터페이스의 이름과 동일하게 지정

 

  🎃  <select> 태그는 반드시 resultType이나 resultMap이라는 속성을 지정
         ➡️  resultType은 말 그대로 select 문의 결과를 어떤 타입으로 처리할지에 대한 설정으로 java.lang.String과 같이 전체 이름을 써야 하지만 자주 사용하는 타입은 string과 같이 사용할 수 있음

 


🎃  마지막으로 root-context.xml에 있는 MyBatis 설정에 XML 파일들을 인식하도록 설정을 추가

 

  🎃  추가된 mapperLocations는 말 그대로 'XML 매퍼 파일들의 위치'를 의미
  🎃  resources의 경우 'classpath:' 접두어를 이용해서 인식되는 경로이고 mapper 폴더 밑에 폴더가 있어도 관계없도록 '**'와 모든 '.xml'을 의미라는 '*.xml'을 지정

 

 

📌  XML 설정이 정상적인지 테스트를 통해서 확인. 기존에 만들어진 TimeMapperTests를 이용.



 

 

 

[ 내용 참고 : IT 학원 강의 ]

 

 


1.  스프링의 시작 

스프링 프레임워크는 웹이라는 제한적 용도로만 쓰이는 것이 아니라 객체지향의 의존성 주입 dependency injection 기법을 적용할 수 있는 객체지향 프레임워크

 

  •   스프링 프레임워크는 로드 존슨이 2002년도에 집필했던 'J2EE 설계 및 개발 wrox'이라는 책의 예제 코드에서 시작되었는데 말 그대로 효과적이고 가볍게 J2EE를 이용할 수 있다는 것을 증명하면서 예제의 코드를 발전시킨 것
  • 2000년 당시 자바 진영에서 JavaEE의 여러가지 스펙을 정의하고 비대해지는 동안 스프링 프레임워크는 반대로 '경량 light weight 프레임워크'를 목표로 만들어짐
  • 스프링이 등장할 때 여러 종류의 프레임워크들이 비슷한 사상(경량화, 단순화)으로 등장했지만 다른 프레임워크들과 달리 스프링 프레임워크는 개발과 설계 전반에 관련된 문제들을 같이 다루었기 때문에 결론적으로 가장 성공한 프레임워크로 기록
  • 스프링 프레임워크는 가장 중요한 '코어core' 역할을 하는 라이브러리와  여러 개의 추가적인 라이브러리를 결합하는 형태로 프로젝트를 구성. 가장 대표적으로 웹 MVC구현을 쉽게 할 수 있는 'Spring Web MVC'나 JDBC 처리를 쉽게 할 수 있는 'MyBatis'를 연동하는 'mybatis-spring'과 같은 라이브러리가 그러한 예. 

2.  의존성 주입 

스프링이 객체지향 구조를 설계할 때 받아들인 개념은 '의존성 주입 dependency injection'이라는 사상.

    ✓ 의존성 주입은 어떻게 하면 '객체와 객체 간의 관계를 더 유연하게 유지할 것인가?'에 대한 고민에서 나옴

    ✓ 즉 의존성이란 하나의 객체가 자신이 해야 하는 일을 하기 위해서 다른 객체의 도움이 필수적인 관계를 의미

    ✓ 스프링 프레임워크는 바로 이런 점을 프레임워크 자체에서 지원. 다양한 방식으로 필요한 객체를 찾아서 사용할 수 있도록 XML 설정이나 자바 설정 등을 이용

 


3.  ApplicationContext 와 빈 Bean

👻  서블릿이 존재하는 공간을 서블릿 콘텍스트 ServletContext라고 했던 것 처럼, 스프링에서는 빈 Bean 이라고 부르는 객체들을 관리하기 위해서 ApplicationContext 라는 존재를 활용

코드의 경우 ApplicationContext는 root-context.xml을 이용해서 스프링이 실행

ApplicationContext 객체가 생성

root-context.xml을 읽으면 SampleService와 SampleDAO가 해당 클래스의 객체를 생성해서 관리

테스트를 실행하면 @Autowired가 처리된 부분에 맞는 타입의 빈 Bean 이 존재하는지를 확인

이를 테스트 코드 실행 시에 주입

멤버변수에 직접 @Autowired를 선언하는 방식을 '필드 주입 Field Injection' 방식

 


4.  SampleDAO 주입하기 

 

@Autowired를 이용하면 필요한 타입을 주입받을 수 있다는 사실을 이용해서 SampleService를 다음과 같이 변경

 

 

 

📌  Lombok의 @ToString을 적용한 부분과 SampleDAO의 변수로 선언하고 @Autowired를 적용

📌  테스트 코드를 실행하면 SampleService 객체 안에 SampleDAO 객체가 주입된 것을 확인

 

 

 

 

 

👾  스프링을 이용할 때는 클래스를 작성하거나 객체를 직접 생성하지 않음

👾 이 역할은 스프링 내부에서 이루어지며 ApplicationContext가 생성된 객체들을 관리

  • 서블릿의 url 매핑의 경우 web.xml에 <servlet>이라는 태그를 이용해서 서블릿 클래스의 이름과 경로를 전부 기록해야 했지만, 최근에는 @WebServlet 어노테이션이 이를 대신하는 것처럼, 스프링도 비슷한 방식으로 발전, 초기 스프링 버전에서는 XML 파일에 <bean>이라는 것을 이용해서 설정하는 방식이 2.5버전 이후에 어노테이션 형태로 변화되면서 예전에 비해 편리하게 설정이 가능

5.  @Repository, @Service 

👩🏻‍💻  서블릿에서도 @WebServlet이나 @WebFilter와 같이 다양한 어노테이션이 존재하듯이 스프링 프레임워크는 애플리케이션 전체를 커버하기 때문에 다양한 종류의 어노테이션을 사용하도록 작성 

 

@Controller      : MVC의 컨트롤러를 위한 어노테이션
@Service           : 서비스 계층의 객체를 위한 어노테이션
@Repository    : DAO와 같은 객체를 위한 어노테이션
@Component   : 일반 객체나 유틸리티 객체를 위한 어노테이션

 

💡  DAO(Data Access Object)   "실제로 DB의 데이터에 접근하는 객체"
  •  DAO는 Service와 DB를 연결하는 역할을 하며, 실제로 DB에 접근하여 data를 삽입, 삭제, 조회, 수정 등 CRUD 기능을 수행

💡 DTO(Data Transfer Object)   "계층 간 데이터 교환을 위한 객체"

  • 로직을 가지지 않고 getter/setter 메소드만 가진 순수한 데이터 객체 클래스(Java Beans)로 DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용

💡 VO(Value Object)   "변경 불가능하며 오직 읽기만 가능 (Read-Only)"

  • DTO는 setter를 가지고 있어 값을 변경할 수 있지만, VO는 getter만을 가지기 때문에 읽기만 가능하고 수정은 불가능

 

출처: https://hstory0208.tistory.com/entry/Spring-DAO-DTO-VO란-각각의-개념에-대해-알아보자 

 

 

👩🏻‍💻  스프링이 사용하는 어노테이션의 경우 웹 영역뿐만 아니라 애플리케이션 전체에 사용할 수 있는 객체들을 포함

👩🏻‍💻  어노테이션을 이용하게 되면 스프링 설정은 <bean>이 아니라 '해당 패키지를 조사해서 어노테이션들을 이용'하는 설정으로 변경

 

설정 변경 전과 후

 

📌  기존 설정과 비교해 보면 XML 위쪽의 xmlns 네임 스페이스가 추가되고 schemaLocation이 변경

📌  내용에는 component-scan이 추가되었는데 속성값으로는 패키지를 지정 

📌  'component-scan'은 해당 패키지를 스캔해서 스프링의 어노테이션들을 인식

 


 

 

 

🎃  SampleDAO는 해당 클래스의 객체가 스프링에서 빈 Bean으로 관리될 수 있도록 @Repository 라는 어노테이션을 추가

 

 

 

 

 

 

 

 

 

🎃  SampleService에는 @Service 어노테이션을 추가

 

 

 

 

 

 

 


 

생성자 주입 방식 

 

👾  초기 스프링에서는 @Autowired를 멤버 변수에 할당하거나, Setter를 작성하는 방식을 많이 이용해 왔지만, 스프링 3 이후에는 생성자 주입이라고 부르는 방식을 더 많이 활용.

👾  생성자 주입 방식은 객체를 생성할 때 문제가 발생하는 지를 미리 확인할 수 있기 때문에 필드 주입이나 Setter 주입 방식 보다 선호되는 방식 

👾  Lombok에서는 생성자 주입을 보다 간단하게 작성할 수 있는데 @RequiredArgsConstructor를 이용해서  필요한 생성자 함수를 자동으로 작성할 수 있음

 

 

 

 

 

💫  생성자 주입 방식은 다음과 같은 규칙으로 작성

   

    - 주입 받아야 하는 객체의 변수는 final로 작성

     - 생성자를 이용해서 해당 변수를 생성자의 파라미터로 지정

 

 

 

 

 

 

 


6.  인터페이스를 이용한 느슨한 결합 

스프링이 의존성 주입을 가능하게 하지만  '좀 더 근본적으로 유연한 프로그램을 설계하기 위해서는 인터페이스를 이용'해서 나중에 다른 클래스의 객체로 쉽게 변경할 수 있도록 하는 것이 좋음.

    ⚡️  예를 들어 코드에서 SampleDAO를 다른 객체로 변경하려면 결론적으로 SampleService 코드 역시 수정해야 함. 추상화된 타입을 이용하면 이러한 문제를 피할 수 있는데 가장 대표적인 것이 인터페이스. 인터페이스를 이용하면 실제 객체를 모르고 타입만을 이용해서 코드를 작성하는 일이 가능해 짐. 

 

1)  SampleDAO를 인터페이스로 변경하기  

 

 

 

🎃  클래스로 작성된 SampleDAO을 인터페이스로 수정. @Repository 삭제

🎃  SampleService는 SampleDAO라는 인터페이스를 보게 되었지만 코드상의 변경은 필요하지 않음

 

 

 

 

 

 

 

 

🎃 SampleDAO 인터페이스는 구현 코드가 없기 때문에 구현한 클래스를 SampleDAOImpl 이라는 이름으로 선언

🎃 SampleDAOImpl에는 @Repository를 이용해서 해당 클래스의 객체를 스프링의 빈 Bean으로 처리되도록 구성

 

 

 

 

 

SampleService의 입장에서는 인터페이스만 바라보고 있기 때문에 실제 객체가 SampleDAOImpl의 인스턴스인지 알수 없지만, 코드를 작성하는 데에는 아무런 문제가 없음. 이처럼 객체와 객체의 의존 관계의 실제 객체를 몰라도 가능하게 하는 방식을 '느슨한 결합 Loose coupling'이라고 함. 느슨한 결합을 이용하면 나중에 SampleDAO 타입의 객체를 다른 객체로 변경하더라도 SampleService 타입을 이용하는 코드를 수정할 일이 없기 때문에 보다 유연한 구조가 됨. 


2) 다른 SampleDAO 객체로 변경해 보기 

 

 

🎃  예를 들어 특정한 기간에만 SampleDAO를 다른 객체로 변경해야 되는 경우 EventSampleDAOImpl이라는 클래스를 작성

 

 

 

 

 

 

 

 

이렇게 되면 SampleService에 필요한 SampleDAO 타입의 빈 Bean이 두 개 SampleDAOImpl, EventSampleDAOImpl 가 되기 때문에 스프링 입장에서는 어떤 것을 주입해야 하는지 알 수 없음테스트 코드를 실행하면 어떤 클래스의 객체를 사용해야 하는지 알 수 없으므로 다음과 같은 에러가 발생

 

Error creating bean with name 'sampleService' defined in file [E:\work\dev\projects\projects-spring intellij\springex\build\classes\java\main\com\nomadlab\springex\sample\SampleService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.nomadlab.springex.sample.SampleDAO' available: expected single matching bean but found 2: eventSampleDAOImpl,sampleDAOImpl 

 

 

스프링이 기대하는 것은 SampleDAO 타입의 객체가 하나 single 이길 기대했지만 2개가 발견. 

이를 해결하는 가장 간단한 방법은 두 클래스 중에 하나를 @Primary라는 어노테이션으로 지정해 주는 것

 


 

3) @Qualifier 이용하기 

 

👾  @Primary를 이용하는 방식 외에도 @Qualifier를 이용하는 방식도 있음

👾  @Qualifier는 이름을 지정해서 특정한 이름의 객체를 주입받는 방식

 

  📌  Lombok과 @Qualifier를 같이 이용하기 위해서는 src/main/java 폴더에 lombok.config 파일을 생성

 

 

 

 

🎃  동작을 확인하기 위해서 SampleDAOImpl과 EventSampleDAOImpl 에는 @Qualifier를 적용. 

🎃  SampleDAOImpl 에는 normal 이라는 이름을 지정 

 

 

 

 

 

 

 

🎃  EventSampleDAOImpl에는 event 라는 이름을 지정 

 

 

 

 

 

 

 

 

 

 

 

🎃  SampleService에는 특정한 이름의 객체를 사용하도록 수정

 

 

 

 

 

 

 

    🎃  normal 이름을 가진 SampleDAOImpl이 주입되는 것을 확인

 


4)  스프링의 빈 Bean 으로 지정되는 객체들 

 

👾  스프링 프레임워크를 이용해서 객체를 생성하고 의존성 주입을 이용할 수 있다는 사실을 알았지만 작성되는 모든 클래스의 객체가 스프링의 빈 Bean으로 처리되는 것은 아님

👾  스프링의 빈 Bean으로  등록되는  객체들은 쉽게 말해서 '핵심 배역'을 하는 객체들로 오랜 시간 동안 프로그램 내에 상주하면서 중요한 역할을 하는 '역할' 중심의 객체들

👾  반대로 DTO나 VO와 같이 '역할' 보다는 '데이터'에 중심을 두고 설계된 객체들은 스프링의 빈 Bean을 등록하지 않음

      특히 DTO의 경우 생명주기가 굉장히 짧고, 데이터 보관이 주된 역할이기 때문에 스프링에서 빈 Bean으로 처리하지 않음


5) XML이나 어노테이션으로 처리하는 객체 

 

👾  빈 Bean 으로 처리할 때 XML 설정을 이용할 수도 있고, 어노테이션을 처리할 수도 있지만, 이에 대한 기준은 '코드를 수정할 수 있는가'로 판단

👾  예를 들어 jar 파일로 추가되는 클래스의 객체를 스프링의 빈Bean으로 처리해야 한다면 해당 코드가 존재하지 않기 때문에 어노테이션을 추가할 수가 없다는 문제가 생김. 이러한 객체들은 XML에서 <bean>을 이용해서 처리하고, 직접 처리되는 클래스는 어노테이션을 이용하는 것이 좋음. 


7.   웹 프로젝트를 위한 스프링 준비 

💡  스프링의 구조를 보면 ApplicatonContext라는 객체가 존재하고 빈으로 등록된 객체들은  ApplicatonContext 내에 생성되어 관리되는 구조.

    ⚡️  이렇게 만들어진 ApplicatonContext가 웹 애플리케이션에 동작하려면 웹 애플리케이션이 실행될 때  스프링을 로딩해서 해당 웹 애플리케이션 내부에 스프링의 ApplicatonContext를 생성하는 작업이 필요하게 되는데 이를 위해서 web.xml을 이용해서 리스너를 설정

    ⚡️  스프링 프레임워크의 웹과 관련된 작업은 'spring-webmvc'라이브러리를 추가해야만 설정이 가능

 

 

 

 

▶️  build.gradle파일에 spring-mvc 라이브러리를 추가

 

 

 

 

▶️  WEB-INF 폴더 아래 web.xml에  <listener>설정과 <listener>에 필요한 <context-param>을 추가

 

 

 

 

💫  설정이 추가된 후에 톰캣을 실행하면 스프링과 관련된 로그가 기록되면서 실행 


8.  DataSource 구성하기 

톰캣과 스프링이 연동되는 구조를 완성했다면 데이터베이스 관련 설정을 추가

 

  📌  build.gradle에 MySQL과 HikariCP 관련 라이브러리를 추가

    // https://mvnrepository.com/artifact/mysql/mysql-connector-java
    implementation 'mysql:mysql-connector-java:8.0.33'

    // https://mvnrepository.com/artifact/com.zaxxer/HikariCP
    implementation 'com.zaxxer:HikariCP:5.0.1'

 

 

   📌  root-context.xml에 HikariCP 설정하기 

    <context:component-scan base-package="com.example.sping_class.sample" />

    <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/sample" />
        <property name="username" value="root"/>
        <property name="password" value="비밀번호"/>
        <property name="dataSourceProperties">
            <props>
                <prop key="cachePrepStmts">true</prop>
                <prop key="prepStmtCacheSize">250</prop>
                <prop key="prepStmtCacheSqlLimit">2048</prop>
            </props>
        </property>
    </bean>

    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <constructor-arg ref="hikariConfig" />
    </bean>

 

  • HikariCP를 사용하기 위해서는 HikariConfig 객체와 HikariDataSource를 초기화해야함
  • root-context.xml을 이용해서 HikariConfig 객체와 HikariDataSource 객체를 설정
  • hikariConfig에는 id 속성이 적용되어 있고 HikariDataSource는 <constructor-arg ref="hikariConfig" />로 id 값을 참조해서 사용
  • HikariDataSource는 javax.sql의 DataSource 인터페이스의 구현체이므로 테스트 코드를 통해서 설정에 문제가 없는지 확인

 

 

 

🎃  root-context.xml에 선언된 HikariCP를 주입받기 위해서 DataSource타입의 변수를 선언하고 @Autowired를 이용해서 주입 되도록 구성

 

 

 

 

 

 

 

 

 

 

 

 

 

 

👩🏻‍💻  스프링은 필요한 객체를 스프링에서 주입해 주기 때문에 개별적으로 클래스를 작성해서 빈으로 등록해 두기만 하면 원하는 곳에서 쉽게 다른 객체를 사용할 수 있음. 이런 특징으로 인해 스프링 프레임워크는 웹이나 데이터베이스 같은 특정한 영역이 아닌 전체 애플리케이션의 구조를 설계할 때 사용

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts