본문 바로가기

kosta_이론

25.05.20 spring framework

spring framework - 자바 기반 오픈소스 프레임워크, java ee에서 요구하는 수준의 기능을 java ee 사용하지 않고 구현위해 시작

특징 

   - 경량 컨테이너 : 객체 생성, 소멸 생명주기 관리하며 필요한 객체 얻어옴

   - 제어의 역행 지원 : 메서드나 객체의 흐름 제어권을 필요에 따라 사용자 코드 호출

   - 의존성 주입 지원 : 각각의 계층이나 서비스 간 의존성이 존재할 경우 연결해 줌

   - 관점 지향 프로그래밍 지원 : 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 분리해 관리

 

스프링은 여러 서브 프로젝트로 구성 > 프로젝트는 여러 모듈로 구성됨 > 세부 프로젝트의 모듈 조합해 개발함

대표적 스프링 프로젝트 - 스프링 부트, 스프링 데이터, 스프링 배치, 스프링 시큐리티, 스프링 hateoas

 

스프링 부트 - 빠르고 간편하게 시작 가능 (특정 목적의 개발환결과 프로젝트 구조 자동 생성)

   톰캣 같은 was 내장, 별도 웹 서버 설치 및 실행 없이 웹 구동 가능

 

리액티브 스택 - 새로운 비동기 논블로킹 i/o 구조 사용해 멀티코어 시스템 장점 살리고 대규모 사용자 접속 처리에 유용함

   톰캣 8.5이상 사용 가능, nosql db 연동 지원

 

서블릿 스택 - 서블릿 api에 기반한 동기 방식 블로킹 i/o 구조 사용 > 하나의 요청은 하나의 스레드로 처리

   Spring MVC 기반의 서버 프로그램 개발과 JPA, JDBC, NoSQL 데이터베이스 지원을 포함함

 

리액티브 프로그래밍 - 변화에 반응하는 모델 기본적으로 비동기i/o 기반 데이터 흐름과 변화 전파에 중점둠

   데이터 흐름 먼저 정의 후 변경되었을 시 연관된 함수나 수식 업데이트 됨 (개발 효율 상승 및 적은 스레드로 많은 부하 처리 가능)

 

IoC - 제어의 역행 

   기존 - 프로그램 시작하는 곳에(main()) 필요한 객체 생성 및 메서드 호출하는 흐름

   ioc- 작업 수행하는 쪽에서 객체 생성 > 프로그램 제어를 자신이 아닌 다른곳에 위임함

특징

   객체가 자신이 사용할 객체 생성하거나 선택하지 않음

   객체 자신이 어떻게 생성되고 사용되는지 알 수 없음

   모든 객체는 제어 권한을 위임 받은 특별한 객체(컨테이너)에 의해 만들어지고 사용됨

스프링의 경우 - 컨테이너에서 객체 생성 및 공급 담당 / 빈 - 컨테이너에서 관리되는 스프링 객체

 

DI - 의존성 주입

   ioc를 시스템적으로 구현하는 방법 (di, dl)

di - 클래스 간 의존관계를 bean 설정 기바능로 컨테이너가 자동으로 연결해주는 방식

dl - 저장소에 저장되어 있는 bean에 접근하기 위해 개발자가 컨테이너에 제공하는 api 이용해 빈 찾음

dl은 컨테이너에 대한 의존성 커짐(불필요한 코드 증가) > di 방식 선호됨

 

AOP - 관점 지향 프로그래밍

   횡단 관심사(cross cutting concern) 분리 허용해 모듈성 증가가 목적

   > 코드 자체를 수정하지 않는 대신 기본 코드에 추가 동작(어드바이스) 정의해 추가된 기능 실행됨

 

스프링 webMVC 

   mvc 기반 웹 개발과 구조적으로 거의 동일함

   컨트롤러 직접 구현할 필요 없이 특정 요청에 대한 메서드 작성하면 됨

   > 불필요한 중복 구현 없음, 구조 리펙토링 고민 필요 없음

모델 - 기존 코드 그대로 사용 가능 > 간결하게 개선 가능 + db 관련 작업 처리 가능

뷰 - jsp 그대로 사용 가능 > 독립적 개발 및 테스트 어려워짐

   jsp 종속은 없음, 타임리프,jsp, 프리마커 등 다양한 템플릿 연동 지원

 

컨트롤러 구현 

   클래스 생성 후 선언부 위에 애너테이션 추가

   @Controller - mvc 컨트롤러로 동작할 클래스 의미함

   @RequestMapping() - 서블릿의 @WebServlet의 request mapping과 같음

   기본적으로 jsx-rs와 유사한 구조로 서블릿과 달리 하나의 컨트롤러 클래스에서 여러 메서드와 요청 다양하게 처리 가능

모델 구현

   기존 코드 완전히 재사용 가능  

   dao 객체 불러와 필요한 메서드 호출함 > jdbc 구조 사용 가능 / jdbc나 jpa 사용해 개선 가능

   do(entity) 클래스 경우 재사용 가능, jpa 도입할 경우 추가로 애너테이션 필요

   

컨트롤러에서 dao 사용                       간략화    public void addProduct(Product p) {

   @Autowired                                                    String sql = "insert into product(name, price) value(?,?)";             

   ProductDAO dao;                                            jdbcTemplateObject.update(sql, p.name, p.price);

                                                                       }

 

restController - 스프링에서 rest api 구현에 사용하는 모듈

   기본적인 개발 구조나 원리는 동일함 > 사용하는 애너테이션 등에서 차이

   메서드에 따른 요청 구분하며 요청 또는 경로 파라미터에 따라 제공받을 수 있음

   json 규격으로 전달되는 데이터를 자바 객체로 매핑 가능 > 리턴타입으로 사용하면 자동으로 json으로 변환됨


   hello() - /api/hello?msg=welcome 형식으로 요청 이루어짐

   hello2() - /api/hello/welcome 형식의 요청에 동작함

 

스프링 빈 - 스프링 컨테이너에 관리되는 자바 객체 > 등록은 설정 xml, 애너테이션, 설정 클래스 등으로 등록

   > 오토와이러잉을 통해 공급됨 > 스프링 부트의 경우 애너테이션을 통한 빈 등록을 기본으로 함

   >>필요한 빈 등록 > 필요한 위치에서 @AutoWired 통해 주입 받아 사용함

 

@Component 관계 

   - @Service, @Controller, @Repository 모두 @Component 상속 받음

   컴포넌트 애너테이션으로 등록한 클래스는 모두 자동으로 빈에 등록됨

 

빈 등록

- @Componenet 사용

- @Configuration ~ @Bean 사용

- 필드 주입 @Autowired사용 > 순환 의존성 문제 발생 가능 / 불변 객체 만들 수 없음


 

실습

 

실행될 index파일

package org.example.demo_test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoTestApplication.class, args);
    }

}
package org.example.demo_test;

import org.springframework.ui.Model;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RestController("/test")
public class DemoTestController {
    @GetMapping("/hello")
    public String hello(Model model) {
            model.addAttribute("title","hello test");
            model.addAttribute("msg","msg");
            return "hello";
    }
    @GetMapping("/hello2")
    @ResponseBody
    public String hello2(Model model) {
        model.addAttribute("title","title");
        model.addAttribute("msg","msg");
        return "hello2";
    }
}

 


뉴스 앱

package org.example.news;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class News {
    
    private int aid;
    private String title;
    private String img;
    private String date;
    private String content;

    @Override
    public String toString() {
       return "News [aid=" + aid + ", title=" + title + ", date=" + date + "]";
    }
}

newsDAO는 그대로 가져옴


package org.example.news;

import org.springframework.stereotype.Component;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

@Component
public class NewsDAO {
    
    final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    final String JDBC_URL = "jdbc:mysql://127.0.0.1:3306/newsdb?serverTimezone=Asia/Seoul";
    
    public Connection open() {
       Connection conn = null;
       try {
          Class.forName(JDBC_DRIVER);
          conn = DriverManager.getConnection(JDBC_URL, "root", "1234");
       } catch (Exception e) {
          e.printStackTrace();
       }
       return conn;
    }

    public void addNews(News n) throws Exception {
       Connection conn = open();

       String sql = "insert into news (title, img, date, content) values(?,?,CURRENT_TIMESTAMP(),?)";
       PreparedStatement pstmt = conn.prepareStatement(sql);
       try(conn; pstmt) {
          pstmt.setString(1, n.getTitle());
          pstmt.setString(2, n.getImg());
          pstmt.setString(3, n.getContent());

          pstmt.executeUpdate();
       }
    }

    public List<News> getAll() throws Exception {
       Connection conn = open();
       List<News> newsList = new ArrayList<>();

       String sql = "select aid, title, date_format(date, '%Y-%m-%d %H:%i:%s') as cdate from news";

       PreparedStatement pstmt = conn.prepareStatement(sql);
       ResultSet rs = pstmt.executeQuery();

       try(conn; pstmt; rs) {
          while(rs.next()) {
             News n = new News();

             n.setAid(rs.getInt("aid"));
             n.setTitle(rs.getString("title"));
             n.setDate(rs.getString("cdate"));

             newsList.add(n);
          }
       }

       return newsList;
    }

    public News getNews(int aid) throws Exception {
       // 1) Connection 을 얻어온다.
       Connection conn = open();
       // 2) sql 을 작성한다.
       String sql = "select aid, title, img, date_format(date, '%Y-%m-%d %H:%i:%s') as cdate, content from news"
             + " where aid=?";
       // 3) pstmt 에 sql 을 적용한다. 매개변수가 있다면 argument를 설정한다.
       PreparedStatement pstmt = conn.prepareStatement(sql);
       pstmt.setInt(1, aid);

       News n = new News();
       // 4) pstmt sql 문을 실행한다. ==> rs 에 담는다.
       ResultSet rs = pstmt.executeQuery();

       try(conn; pstmt; rs) {
          // 5) 가져온 데이터를 처리한다.
          if(rs.next()) {
             n.setAid(rs.getInt("aid"));
             n.setTitle(rs.getString("title"));
             n.setImg(rs.getString("img"));
             n.setDate(rs.getString("cdate"));
             n.setContent(rs.getString("content"));
          };
          // 6) 리소스를 닫는다. try with resources 에 의해 자동으로 닫힘
       }
       // 7) 데이터베이스로부터 가져온 데이터를 반환한다.
       return n;
    }

    public void delNews(int aid) throws Exception {
       Connection conn = open();

       String sql = "delete from news where aid = ?";
       PreparedStatement pstmt = conn.prepareStatement(sql);
       pstmt.setInt(1, aid);

       try(conn; pstmt) {
          if(pstmt.executeUpdate() == 0) {
             throw new SQLException("DB - news 삭제 에러");
          };
       }
    }
}

 

NewsApiController

package org.example.news;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/news")
public class NewsApiController {
    final NewsDAO newsDAO;
    @Autowired
    public NewsApiController(NewsDAO newsDAO) {
        this.newsDAO = newsDAO;
    }

    @GetMapping
    public List<News> getAllNews() {
        List<News> newsList = null;
        try {
            newsList = newsDAO.getAll();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
        return newsList;
    }

    @GetMapping("/{aid}")
    public News getNewsById(@PathVariable int aid) {
        News news = null;
        try {
            news = newsDAO.getNews(aid);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return news;
    }
    @PostMapping
    public String addNews(@RequestBody News news) {
        try {
            newsDAO.addNews(news);
        } catch (Exception e) {
            e.printStackTrace();
            return "news api 등록 실패";
        }
        return "news api 등록 성공";
    }
    @DeleteMapping("/{aid}")
    public String delNews(@PathVariable int aid) {
        try {
            newsDAO.delNews(aid);
        } catch (Exception e) {
            e.printStackTrace();
            return "news api 삭제 실패";
        }
        return "news api 삭제 성공";
    }
}

 

NewsWebController

package org.example.news;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/news")
public class NewsWebController {
    final NewsDAO newsDAO;

    @Autowired
    public NewsWebController(NewsDAO newsDAO) {
        this.newsDAO = newsDAO;
    }

    //application.properties에 입력한 저장경로
    @Value("${news.imgdir}")
    String fdir;

    @GetMapping("/list")
    public String news(Model m) throws Exception {
        m.addAttribute("title", "News");
        return "news/newsList";
    }
    @GetMapping("/{aid}")
    public String news(@PathVariable int aid, Model model) {
        News news = null;
        try {
            news = newsDAO.getNews(aid);
            model.addAttribute("news", news);
        } catch (Exception e) {
            model.addAttribute("error","뉴스 조회 실패");
        }
        return "news/newsView";
    }
}

'kosta_이론' 카테고리의 다른 글

25.05.22 스프링 테스트 코드  (2) 2025.05.22
25.05.21 spring framework  (0) 2025.05.22
25.05.12 jsp, 디자인패턴  (0) 2025.05.12
25.05.09 JSP (eclipse)  (1) 2025.05.09
2025.04.19 게시판 crud 만들기  (1) 2025.04.19