class 34 - 스프링 : 3Tier 아키텍처

yoneeki·2023년 3월 22일
0

training-jp

목록 보기
24/31

스프링의 특징

  • 의존성 주입, 제어 역전, 빈 등

스프링의 DI(Dependency Injection)는 객체지향 개발에서 인스턴스 생성 및 관리에 대한 일부 책임을 객체간의 관계를 정의하는 외부 시스템으로 옮기는 개념입니다. 이를 통해 결합도(coupling)를 낮추고 유연한 코드를 작성할 수 있습니다.

스프링에서 DI를 구현하는 방식 중 하나는 제어역전(Inversion of Control, IoC)입니다. 제어역전은 프로그램의 제어 흐름을 뒤집는 것으로, 일반적으로 객체 생성과 관리에 대한 책임을 프레임워크 또는 컨테이너에게 위임합니다.

스프링에서는 DI를 구현하기 위해 ApplicationContext라는 컨테이너를 제공합니다. ApplicationContext는 애플리케이션 컴포넌트들의 생성과 관리를 담당하며, 빈(bean)이라는 개념을 이용해 객체들을 관리합니다. 빈은 스프링 컨테이너에 등록되어 관리되는 객체를 말하며, 빈의 생성, 의존성 주입, 소멸 등의 라이프사이클을 스프링이 제어합니다.

즉, 제어역전을 이용한 DI는 개발자가 객체를 생성하고 관리하는 것이 아니라 스프링 컨테이너가 객체를 생성하고 관리하며, 필요한 곳에 필요한 객체를 주입해줌으로써 개발자가 더욱 집중해야 할 핵심 로직에 더 집중할 수 있도록 도와줍니다.

스프링의 3-Tier 아키텍처

  1. Dao - SqlMapper.xml : 함수 반환형을 정하고 서로 매핑만 함. Dao는 철저하게 DB와 연결되는 역할만 담당함
  2. Service : 인터페이스인 Dao를 @Autowired로서 객체를 주입해 불러와(스프링의 DI-제어 역전), 자료형이 반환되기까지의 구체적인 로직을 작성함
  3. Controller : Service를 @Autowired로 불러와서 Model 객체에 담아 View 단의 URL로 보냄




SpringBoot으로 CRUD 구현

환경설정

  • Maven : ThymeLeaf, ojdbc8 등등 추가
  • YAML
# 서버 포트 설정
server:
  port: 9090

# 스프링 로그 예쁘게 나오기 설정
spring:
  output:
    ansi:
      enabled: always
      
  # 데이터 베이스 연동
  datasource:
    driver-class-name: oracle.jdbc.driver.OracleDriver
    url:  jdbc:oracle:thin:@localhost:1521:xe
    username: "---------"
    password: "---------"

#로그 찍기    
logging:
  level:  
    '[com.jjang051.ch04]': DEBUG

#mybatis 설정
mybatis:
  mapper-locations: classpath:sqlmapper/**/*.xml
  • main - resources 밑에 sqlmapper 폴더를 만들고 그 안에 mapper xml 파일을 추가한다.

BoardMapper.xml

  • resources 폴더 아래 sqlmapper 폴더 안에 BoardMapper.xml
  • namespace는 mapper로 연결한 dao 클래스가 있는 정확한 경로
  • parameterType이 dto라면 역시나 경로로 연결할 것
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jjang051.ch04.dao.BoardDao">
  <select id="getAllBoard" resultType="com.jjang051.ch04.dto.BoardDto">
    SELECT * FROM BOARD03
  </select>
  
  <select id="getOneBoard" resultType="com.jjang051.ch04.dto.BoardDto">
    SELECT * FROM BOARD03 WHERE no = #{no}
  </select>

  <insert id="insertBoard" parameterType="com.jjang051.ch04.dto.BoardDto">
    INSERT INTO BOARD03 VALUES (BOARD03_SEQ.NEXTVAL,
                                                    #{userName},
                                                    #{subject},
                                                    #{contents},
                                                    SYSDATE,0)
  </insert>

  <update id="updateHit" parameterType="Integer">
		UPDATE BOARD03 SET HIT = HIT + 1 WHERE NO = #{no}
	</update>

	<update id="updateBoard" parameterType="com.jjang051.ch04.dto.BoardDto">
		UPDATE BOARD03 SET 
    USERNAME = #{userName}, SUBJECT = #{subject}, CONTENTS = #{contents} 
		WHERE NO = #{no}
	</update>

  <delete id="deleteBoard" parameterType="Integer">
    DELETE FROM BOARD03 WHERE no = #{no}
  </delete>
</mapper>

Dao

  • @Mapper를 사용하면 mapper xml파일과 dao 함수를 바로 연결할 수 있다.
  • Dao는 철저하게 DB에 연결하는 용도로만 사용하기에 인터페이스.
  • 구체적인 데이터 반환은 Service로 넘어가서 실행.
package com.jjang051.ch04.dao;

import com.jjang051.ch04.dto.BoardDto;
import java.util.ArrayList;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BoardDao {
  ArrayList<BoardDto> getAllBoard();

  public void insertBoard(BoardDto boardDto);

  public BoardDto getOneBoard(int no);

  public void updateBoard(BoardDto boardDto);

  public void deleteBoard(int no);

  public void updateHit(int no);
}

Service

  • Autowired로 인터페이스인 Dao에 의존성을 주입해 싱글턴 객체로 가져온다.
  • Service 클래스에서 dao의 결과를 반환시킨다.
package com.jjang051.ch04.service;

import com.jjang051.ch04.dao.BoardDao;
import com.jjang051.ch04.dto.BoardDto;
import java.util.ArrayList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class BoardService {

  @Autowired
  BoardDao boardDao;

  public BoardService() {
    log.info("=========boardService===========");
  }

  public ArrayList<BoardDto> getAllBoard() {
    ArrayList<BoardDto> boardList = boardDao.getAllBoard();
    //log.info("boardList=={}", boardList);
    return boardList;
  }

  public void insertBoard(BoardDto boardDto) {
    boardDao.insertBoard(boardDto);
  }

  public BoardDto getOneBoard(int no) {
    BoardDto boardDto = boardDao.getOneBoard(no);
    return boardDto;
  }

  public void updateBoard(BoardDto boardDto) {
    boardDao.updateBoard(boardDto);
  }

  public void deleteBoard(int no) {
    boardDao.deleteBoard(no);
  }

  public void updateHit(int no) {
    boardDao.updateHit(no);
  }
}

Controller

  • 파라미터들을 주고 받으며 view단의 주소를 연결시키는 Controller
  • view에서 넘어간 파라미터 === Controller에 매핑된 함수가 받는 파라미터(인자)
  • <input type="text" name="userName"> 의 경우에 name이 Dto의 변수와 이름이 동일하고, 이미 이 관련해서 dao와 mapper가 잘 연결되어 있으면 Controller가 알아서 얘가 Dto의 변수라고 생각한다.
  • 바인딩 : 그래서 Controller의 public String updateProcess(BoardDto boardDto, int no) 함수에는 개별적으로 userName, subject, contents가 넘어갔지만 setter 함수 사용 없이도 저 argument boardDto에 저절로 userName, subject, contents가 빨려 들어간다.
  • String userName = request.getParameter("userName"), dto.setUserName(userName) 과 같은 작업이 스프링에서는 사라지는 것이다.
  • 관련 개념 : @ModelAttribute - 스프링은 객체로 Parameter를 넘길 수 있다 (JSP는 오로지 String만 넘김)
package com.jjang051.ch04.controller;

import com.jjang051.ch04.dto.BoardDto;
import com.jjang051.ch04.service.BoardService;
import java.io.IOException;
import java.util.ArrayList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/board")
@Slf4j
public class BoardController {

  @Autowired
  BoardService boardService;

  @GetMapping("/list")
  public String list(Model model) {
    //BoardService boardService = new BoardService();
    ArrayList<BoardDto> boardList = boardService.getAllBoard();
    model.addAttribute("boardList", boardList);
    return "/board/list";
  }

  @GetMapping("/write")
  public String write() {
    return "/board/write";
  }

  @PostMapping("/writeProcess")
  public String writeProcess(BoardDto boardDto) {
    //log.info("boardDto=={}", boardDto);
    boardService.insertBoard(boardDto);
    return "redirect:/";
  }

  @GetMapping("/view")
  public String view(Model model, int no) {
    log.info("no : {}", no);
    BoardDto boardDto = boardService.getOneBoard(no);
    boardService.updateHit(no);
    model.addAttribute("boardDto", boardDto);
    return "board/view";
  }

  @GetMapping("/update")
  public String update(Model model, int no) {
    log.info("no : {}", no);
    BoardDto boardDto = boardService.getOneBoard(no);
    model.addAttribute("boardDto", boardDto);
    return "board/update";
  }

  @PostMapping("/updateProcess")
  public String updateProcess(BoardDto boardDto, int no) {
    log.info("updateProcess no : {}", no);
    boardService.updateBoard(boardDto);
    return "redirect:/board/view?no=" + no;
  }

  @GetMapping("/delete")
  public String delete(Model model, int no) {
    log.info("no : {}", no);
    boardService.deleteBoard(no);
    return "redirect:/board/list";
  }
}

list.html

<!DOCTYPE html>
<html xmlns="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Board List</title>
    <style>
        .table {
            text-align: center;
        }
    </style>
</head>
<body>
    <h1> Board List </h1>
    <hr>
    <table class="table">
        <colgroup>
            <col width="100px" height="30px" style="background: #ffffff" />
            <col width="300px" height="30px" style="background: #eeeeee" />
            <col width="400px" height="30px" style="background: #999999" />
            <col width="200px" height="30px" style="background: #eeeeee" />
            <col width="50px" height="30px" style="background: #999999" />
        </colgroup>
        <tr>
            <th>번호</th>
            <th>이름</th>
            <th>제목</th>
            <th>날짜</th>
            <th>조회수</th>
        </tr>
 
        <tr th:each="board : ${boardList}">
            <td><a th:href="@{/board/view(no = ${board.no})}">[[${board.no}]]</a></td>
            <td th:text="${board.userName}"></td>
            <td th:text="${board.subject}"></td>
            <td th:text="${board.regDate}"></td>
            <td th:text="${board.hit}"></td>
        </tr>

    </table>
</body>
</html>

view.html

<!DOCTYPE html>
<html xmlns="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VIEW List</title>
    <style>
        .table {
            text-align: center;
        }
    </style>
</head>
<body>
    <h1> Board List </h1>
    <hr>
    <table class="table">
    <tr>
        <td style="width:20%; background-color:#ffffff">번호</td>
        <td>[[${boardDto.no}]]</td>
    </tr>
      <tr>
        <td style="width:20%; background-color:#eeeeee">이름</td>
        <td>[[${boardDto.userName}]]</td>
      </tr>
      <tr>
        <td style="width:20%; background-color:#eeeeee">제목</td>
        <td>[[${boardDto.subject}]]</td>
      </tr>
      <tr>
        <td style="width:20%; background-color:#eeeeee">게시일</td>
        <td>[[${boardDto.regDate}]]</td>
      </tr>
      <tr>
        <td style="width:20%; background-color:#eeeeee">조회수</td>
        <td>[[${boardDto.hit}]]</td>
      </tr>
      <tr>
        <td style="width:20%; background-color:#eeeeee">내용</td>
        <td>[[${boardDto.contents}]]</td>
      </tr>
    </table>
    <a th:href="@{/board/delete(no = ${boardDto.no})}"><button>삭제하기</button></a>
    <a th:href="@{/board/update(no = ${boardDto.no})}"><button>수정하기</button></a>
    <a href="/board/list"><button>리스트로 돌아가기</button></a>
</body>
</html>

write.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>write</h1>
  <form action="/board/writeProcess" method="post">
    <div><input type="text" name="userName"></div>
    <div><input type="text" name="subject"></div>
    <div><textarea name="contents" id="" cols="30" rows="10"></textarea></div>
    <div><button>확인</button></div>
  </form>
</body>
</html>

update.html

<!DOCTYPE html>
<html xmlns="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>UPDATE</title>
</head>
<body>
  <h1>UPDATE</h1>
  <form th:action="@{/board/updateProcess(no = ${boardDto.no})}"  method="post">
    <div>이름 : <input type="text" name="userName" th:placeholder="${boardDto.userName}"></div>
    <div>제목 : <input type="text" name="subject" th:placeholder="${boardDto.subject}"></div>
    <div>내용 : <textarea name="contents" id="" cols="30" rows="10" th:placeholder="${boardDto.contents}"></textarea></div>
    <div><button>수정하기</button></div>
  </form>
</body>
</html>

살펴볼 점

  • html의 input에서 Controller로 (Controller 안에 해당하는 경로의 작업으로) 넘길 때, Dto에 set을 하지 않더라도! 이미 Dao에서 파라미터를 Dto를 받기로 했다면! 나는 그냥 userName을 넘겼는데 Controller는 set 작업 없이, userName을 받았는데 저절로 boardDto.userName으로 해석해 받는다.

  • 타임리프에서 태그를 사용할 때
    <form th:action="@{/board/updateProcess(no = ${boardDto.no})}" method="post">
    <a th:href="@{/board/delete(no = ${boardDto.no})}"><button>삭제하기</button></a>
    처럼, 사용법이 특이하기 때문에 주의해서 사용할 것.

Config

  • mapper에 있는 몇 기능을 config로 뺄 수 있다

yml

# 서버 포트 설정
server:
  port: 9090

# 스프링 로그 예쁘게 나오기 설정
spring:
  output:
    ansi:
      enabled: always
      
  # 데이터 베이스 연동
  datasource:
    driver-class-name: oracle.jdbc.driver.OracleDriver
    url:  jdbc:oracle:thin:@localhost:1521:xe
    username: "--------------"
    password: "--------------"

#로그 찍기    
logging:
  level:  
    '[com.jjang051.ch04]': DEBUG

#mybatis 설정
mybatis:
  mapper-locations: classpath:sqlmapper/**/*.xml
  config-location: classpath:sqlmapper/config/config.xml

config

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <typeAliases>
    <typeAlias type="com.jjang051.ch04.dto.BoardDto" alias="BoardDto" />
  </typeAliases>
</configuration>

mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jjang051.ch04.dao.BoardDao">
  <select id="getAllBoard" resultType="BoardDto">
    SELECT * FROM BOARD03
  </select>
  
  <select id="getOneBoard" resultType="BoardDto">
    SELECT * FROM BOARD03 WHERE no = #{no}
  </select>

  <insert id="insertBoard" parameterType="BoardDto">
    INSERT INTO BOARD03 VALUES (BOARD03_SEQ.NEXTVAL,
                                                    #{userName},
                                                    #{subject},
                                                    #{contents},
                                                    SYSDATE, 0)
  </insert>

  <update id="updateHit" parameterType="Integer">
		UPDATE BOARD03 SET HIT = HIT + 1 WHERE NO = #{no}
	</update>

	<update id="updateBoard" parameterType="BoardDto">
		UPDATE BOARD03 SET 
    USERNAME = #{userName}, SUBJECT = #{subject}, CONTENTS = #{contents} 
		WHERE NO = #{no}
	</update>

  <delete id="deleteBoard" parameterType="Integer">
    DELETE FROM BOARD03 WHERE no = #{no}
  </delete>
</mapper>
profile
Working Abroad ...

0개의 댓글