TIL - 14주차 코드

 


1. 스프링 부트 : 230227

  • 4대 저장소 : Session 저장소, Request 저장소
  • Application 저장소 = servletContext의 형식 명칭
  • Page 저장소 = pageContext 명칭

1) 파일 전송 설정!

  • 파일 전송의 location 설정은 기본 설정으로 하자. theshold 설정도 디스크가 사용하는 메모리량이라서 우리가 설정할 부분이 아니다.

  • application.properties 설정!

spring.servlet.multipart.max-file-size=100MB	
#각각의 파일 용량 
spring.servlet.multipart.max-request-size=200MB 
#전체 용량


2) 파일 저장하는 방법

  • 이미지는 webapp/image 폴더에 만들자!

  • 파일을 저장하기 위해서는 절대 경로만으로 스트림을 만들 수 있다? 다른 경로도 필요하다!
    • 그 이유는 다른 디렉토리에서 서비스가 동작할 수 있으면 경로가 다 달라지기 때문이다.
  • 따라서, urlPath와 물리적 경로인 realPath가 필요하다. 스프링이 대신 해주지 못하고 기존 서블릿 코드를 가져다가 사용하자!

  • 아직 스프링 부트는 webapp 안에만 이미지를 넣을 수 있다. 아직 다른 폴더에 넣을 수 없다. 기술 발전이 되지 않았다.


  • 실습 코드 :
    • 파일을 전송하기 위한 2가지 방법 :
package kr.co.rland.web.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

// FrontController(POJO 클래스)를 만드는 방법! 
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!) 
@Controller
@RequestMapping("/")
public class HomeController {
	
	//업로드는 요청이므로 Post요청이다. 그리고 파일 전송은 view가 필요 없다!
	@PostMapping("upload")
	@ResponseBody		
	public String upload(MultipartFile img, HttpServletRequest request) throws IOException {
		
		// 4대 저장소 : session, request
		// application = 서블릿 컨텍스트의 형식 명칭
		// page = pageContext 명칭
		
		String urlPath = "/image/menu/" + img.getOriginalFilename();
		String realPath = request.getServletContext().getRealPath(urlPath);
		
//		realPath = this.getClass().getResource("").getPath();
//		realPath = this.getClass().getResource("/").getPath();	// classes라는 root부터 시작 
		// realPath는 클래스 파일이 아니라 실제 폴더에서 파일의 위치이다. 
		
		System.out.println(realPath);
		
		
		// 파일을 저장하는 방법 1: 
//		String fileName = img.getOriginalFilename();
//		
//		InputStream fis = img.getInputStream();
//		OutputStream fos = new FileOutputStream(realPath);
//		
//		byte[] buf = new byte[1024];		
//		int size = 1024;					// 버퍼의 데이터를 1024 바이트씩 읽는다.
//		
//											// fis.read()에 의해서 데이터를 1줄씩 읽는다. 
//		while((size = fis.read(buf))!=-1)	// -1의 의미 : 읽는 게 더 이상 없으면 파일을 저장하지 않음. 
//			fos.write(buf, 0, size);	// buf에 0번부터 size까지 버퍼에 저장하여 파일을 생성한다.
//		
//		fis.close();
//		fos.close();
		
		
		// 파일을 저장하는 방법 2 : 
		img.transferTo(new File(realPath));	// 이것도 가능하지만, 직접 수정하거나 출력할 수 없다.
		
		
		// return 되는 것이 바로 문자열로 반환할 때 Body 사용!
		System.out.println(img.getOriginalFilename());
		return realPath;
	}
	
	// 함수 이름은 보통 url이 된다.
	@RequestMapping("index")			
	public String index(Model model, HttpServletResponse response) throws UnsupportedEncodingException {	// 이렇게 하면, Front-controller가 가지고 있는 response를 가져온다.
		
		// String data = "Hello Data";
		
		// 쿠키도 서블릿이라서 어떻게 해야하지? 방법이 없어서 어쩔 수 없이 response를 사용한다. 
		// 쿠키는 한글을 출력 못해서 url 인코더로 인코딩을 해주어야 한다. ㅜ
		
		String data = URLEncoder.encode("cookie 지륭~", "utf-8"); 
		System.out.println(data);
		
		Cookie cookie = new Cookie("my", data);
		response.addCookie(cookie);
		// 쿠키는 모든 url에서 열어 볼 수 있다. 이러한 url은 클라이언트가 요청한 url이다.("/"에서 시작)
		//model은 addAttribute를 해서 데이터를 view단으로 출력해준다. 
		
		model.addAttribute("data", data);
		
		return "/WEB-INF/view/index.jsp";
	}
	

}




2) 2개 이상의 파일을 전송하는 방법

  • HomeController.java
package kr.co.rland.web.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

// FrontController(POJO 클래스)를 만드는 방법! 
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!) 
@Controller
@RequestMapping("/")
public class HomeController {
	
	//업로드는 요청이므로 Post요청이다. 그리고 파일 전송은 view가 필요 없다!
	@PostMapping("upload")
	@ResponseBody		
	public String upload(MultipartFile[] imgs, HttpServletRequest request) throws IOException {
		
		// 4대 저장소 : session, request
		// application = 서블릿 컨텍스트의 형식 명칭
		// page = pageContext 명칭
		
		for(int i=0; i<imgs.length; i++)
		{
			MultipartFile img = imgs[i];
			
			if(img.isEmpty())	// 파일 저장되는 순서 생각해보기(2개 파일 중 순서)
				continue;		// break가 아니라 continue이다.
			
			String urlPath = "/image/menu/" + img.getOriginalFilename();
			String realPath = request.getServletContext().getRealPath(urlPath);
			
	//		realPath = this.getClass().getResource("").getPath();
	//		realPath = this.getClass().getResource("/").getPath();	// classes라는 root부터 시작 
			// realPath는 클래스 파일이 아니라 실제 폴더에서 파일의 위치이다. 
			
			System.out.println(realPath);
			
	
		// 파일을 저장하는 방법 1: 
//		String fileName = img.getOriginalFilename();
//		
//		InputStream fis = img.getInputStream();
//		OutputStream fos = new FileOutputStream(realPath);
//		
//		byte[] buf = new byte[1024];		
//		int size = 1024;					// 버퍼의 데이터를 1024 바이트씩 읽는다.
//		
//											// fis.read()에 의해서 데이터를 1줄씩 읽는다. 
//		while((size = fis.read(buf))!=-1)	// -1의 의미 : 읽는 게 더 이상 없으면 파일을 저장하지 않음. 
//			fos.write(buf, 0, size);	// buf에 0번부터 size까지 버퍼에 저장하여 파일을 생성한다.
//		
//		fis.close();
//		fos.close();
		
		
			// 파일을 저장하는 방법 2 : 
			img.transferTo(new File(realPath));	// 이것도 가능하지만, 직접 수정하거나 출력할 수 없다.
			
			// return 되는 것이 바로 문자열로 반환할 때 Body 사용!
			System.out.println(realPath);
		
		}
		
		return "ok";
	}
	
	// 함수 이름은 보통 url이 된다.
	@RequestMapping("index")			
	public String index(Model model, HttpServletResponse response) throws UnsupportedEncodingException {	// 이렇게 하면, Front-controller가 가지고 있는 response를 가져온다.
		
		// String data = "Hello Data";
		
		// 쿠키도 서블릿이라서 어떻게 해야하지? 방법이 없어서 어쩔 수 없이 response를 사용한다. 
		// 쿠키는 한글을 출력 못해서 url 인코더로 인코딩을 해주어야 한다. ㅜ
		
		String data = URLEncoder.encode("cookie 지륭~", "utf-8"); 
		System.out.println(data);
		
		Cookie cookie = new Cookie("my", data);
		response.addCookie(cookie);
		// 쿠키는 모든 url에서 열어 볼 수 있다. 이러한 url은 클라이언트가 요청한 url이다.("/"에서 시작)
		//model은 addAttribute를 해서 데이터를 view단으로 출력해준다. 
		
		model.addAttribute("data", data);
		
		return "/WEB-INF/view/index.jsp";
	}
	

}




  • reg.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<section>
		<h1>메뉴 등록 페이지 </h1>
		<form method="post">
			<fieldset>
				<legend>메뉴 입력 필드</legend>
				<div>
					<label>제목 : </label>
					<input type="text" name="title">
				</div>
				<div>
					<input type="submit" value="등록">
				</div>
			</fieldset>
		</form>
	</section>
	
	
	<section>
		<h1>파일 입력 필드</h1>
		<form action="/upload" method="post" enctype="multipart/form-data">
			<fieldset>
				<legend>이미지: </legend>
				<div>
					<label>이미지 : </label>
					<input type="file" name="imgs">
				</div>
				<div>
					<label>이미지 : </label>
					<input type="file" name="imgs">
				</div>
				<div>
					<input type="submit" value="등록">
				</div>
			</fieldset>
		</form>
	</section>
</body>
</html>



3) MariaDB와 Spring 연동시키기

  • MySQL은 무료가 아닌 무료이다. 서비스가 재판매가 불가능하다. 평가판 정도가 가능하다.

  • 그래서, MariaDB를 사용한다.

  • MySQL을 설치해서 java에서 연결을 mariaDB로 사용하자.

  • mariaDB를 연결하고 연결이 잘되었는지 JUnit Test를 해보자

    • Run As에서 JUnit으로 실행해보자!


  • 실습 코드 요약 :


MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import kr.co.rland.web.entity.Menu;

public interface MenuRepository {
	List<Menu> findAll();
}


JdbcMenuRepository.java
package kr.co.rland.web.repository.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

public class JdbcMenuRepository implements MenuRepository {

	@Override
	public List<Menu> findAll() {
		String sql = String.format("select id, name, price, regDate, categoryId from menu");
		
		List<Menu> list = new ArrayList<>();

		try {
			Class.forName("org.mariadb.jdbc.Driver");
			String url = "jdbc:mariadb://db.newlecture.com:3306/rlanddb";
			Connection con = DriverManager.getConnection(url, "rland", "20220823");

			Statement st = con.createStatement();
			ResultSet rs = st.executeQuery(sql);

			// 필터링, 집계, 정렬
			while (rs.next()) // 서버의 커서를 한칸 내리고 그 위치의 레코드를 옮겨 오는 것. -> 레코드 하나가 저장되는 공간은?
			{
				long id = rs.getInt("id");
				String name = rs.getString("name");
				int price = rs.getInt("price");
				Date regDate = rs.getDate("regDate");
				int categoryId = rs.getInt("categoryId");
				
				Menu menu = new Menu(id, name, price, regDate, categoryId, 1);
				list.add(menu);
			}
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

}


Menu.java
package kr.co.rland.web.entity;

import java.util.Date;

public class Menu {
	private long id;
	private String name;
	private int price;
	private Date regDate;
	private int categoryId;
	private long regMemberId;
	
	
	public Menu() {
	}
	
	
	public Menu(long id, String name, int price, Date regDate, int categoryId, long regMemberId) {
		this.id = id;
		this.name = name;
		this.price = price;
		this.regDate = regDate;
		this.categoryId = categoryId;
		this.regMemberId = regMemberId;
	}
	
	//insert용
	
	public Menu(String name, int price, int categoryId) {
		this.name = name;
		this.price = price;
		this.categoryId = categoryId;
	}


	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public int getPrice() {
		return price;
	}


	public void setPrice(int price) {
		this.price = price;
	}


	public Date getRegDate() {
		return regDate;
	}


	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}


	public int getCategoryId() {
		return categoryId;
	}


	public void setCategoryId(int categoryId) {
		this.categoryId = categoryId;
	}


	public long getRegMemberId() {
		return regMemberId;
	}


	public void setRegMemberId(long regMemberId) {
		this.regMemberId = regMemberId;
	}


	@Override
	public String toString() {
		return "Menu [id=" + id + ", name=" + name + ", price=" + price + ", regDate=" + regDate + ", categoryId="
				+ categoryId + ", regMemberId=" + regMemberId + "]";
	}
	
	
	
}



  • JUnit Test 방법 :


JdbcMenuRepository.java
package kr.co.rland.web.repository.jdbc;

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

import java.util.List;

import org.junit.jupiter.api.Test;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

class JdbcMenuRepositoryTest {

	@Test
	void testFindAll() {
		MenuRepository repository = new JdbcMenuRepository();
		
		// 이렇게 써주던가 assert 계열 메서드의 매크로를 사용하자! 
//		if(repositoty.findAll()==null)	
		
		List<Menu> list = repository.findAll();
		
		System.out.println(list.size());
		
	}

}




2. Spring DI - XML : 230228

  • 서비스 레이어(코드를 레이어로 나눈 것)의 문제점 : 트랜잭션 처리가 힘들다.

1) 트랜잭션 :

  • 논리적인 업무 단위이다.

  • 물리적인 업무단위는 insert, update, delete를 의미한다.

  • 예를 들어, 계좌 이체를 물리적으로 update를 2번해야 한다. 이것을 한번에 처리해야 하므로 하나의 업무로 봐서 ‘트랜잭션’이라고 한다.

  • 즉, 한 번에 실행해야 할 업무 단위이다. 논리적인 명령 단위이다.

  • 우리는 트랜잭션이 깨지면 처리를 해주어야 한다. 하지만, 비즈니스를 3-layer로 나누면 트랜잭션이 깨지면 처리하기 힘들다.

  • 나중에 AOP 방법론으로 트랜잭션 처리가 가능하다.




2) 오라클의 JDBC Template :

  • 먼저, 우리는 스프링을 쓰지않고 DB와 연결하기 위해서 오라클의 JDBC Template를 이용한다.
  • DB를 이용하기 위해서 사용하는 연결 정보인 dataSource를 과거에는 스프링을 이용하지 않고도 이용할 수 있었다.


  • 오라클의 JDBC Template는 query 메서드를 이용하면서 query 메서드 한 줄로 아래에 있는 JDBC 기본 코드의 rs.next를 이용한 긴 while문을 대신할 수 있었다.
  • 아래 예시 코드로는 JUnit test만 가능하고 톰캣을 이용한 서버는 아직 이용이 불가능하다.


MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import kr.co.rland.web.entity.Menu;

public interface MenuRepository {
	List<Menu> findAll();
}


JdbcMenuRepository.java

package kr.co.rland.web.repository.jdbc;

import java.util.List;

import javax.sql.DataSource;

import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

// @Component // @Component는 IOC 컨테이너에서 새로운 빈 객체를 만들어 준다. 
public class JdbcMenuRepository implements MenuRepository {

	@Override
	public List<Menu> findAll() {
		
		
		String sql = "select id, name, price, regDate, categoryId from menu";

		DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
		dataSourceBuilder.driverClassName("org.mariadb.jdbc.Driver");
		dataSourceBuilder.url("jdbc:mariadb://db.newlecture.com:3306/rlanddb");
		dataSourceBuilder.username("rland");
		dataSourceBuilder.password("20220823");

		DataSource dataSource = dataSourceBuilder.build();

		JdbcTemplate template = new JdbcTemplate(dataSource);		
		List<Menu> list = template.query(sql, new BeanPropertyRowMapper(Menu.class)); // 메서드의 종류가 많다는 것은 좋은 신호이다.
		// query 메서드를 이용하면서 query 메서드 한 줄로 아래에 있는 JDBC 기본 코드의 rs.next를 이용한 긴 while문을 대신할 수 있었다.
		
		return list;
	}

	
	
//	@Override
//	public List<Menu> findAll() {
//		String sql = String.format("select id, name, price, regDate, categoryId from menu");
//		
//		List<Menu> list = new ArrayList<>();
//
//		try {
//			Class.forName("org.mariadb.jdbc.Driver");
//			String url = "jdbc:mariadb://db.newlecture.com:3306/rlanddb";
//			Connection con = DriverManager.getConnection(url, "rland", "20220823");
//
//			Statement st = con.createStatement();
//			ResultSet rs = st.executeQuery(sql);
//
//			// 필터링, 집계, 정렬
//			while (rs.next()) // 서버의 커서를 한칸 내리고 그 위치의 레코드를 옮겨 오는 것. -> 레코드 하나가 저장되는 공간은?
//			{
//				long id = rs.getInt("id");
//				String name = rs.getString("name");
//				int price = rs.getInt("price");
//				Date regDate = rs.getDate("regDate");
//				int categoryId = rs.getInt("categoryId");
//				
//				Menu menu = new Menu(id, name, price, regDate, categoryId, 1);
//				list.add(menu);
//			}
//			con.close();
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//		return list;
//	}

}



JdbcRepositoryTest.java
package kr.co.rland.web.repository.jdbc;

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

import java.util.List;

import org.junit.jupiter.api.Test;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

class JdbcMenuRepositoryTest {

	@Test
	void testFindAll() {
		MenuRepository repository = new JdbcMenuRepository();
		
		// 이렇게 써주던가 assert 계열 메서드의 매크로를 사용하자! 
//		if(repositoty.findAll()==null)	
		
		List<Menu> list = repository.findAll();
		
		System.out.println(list.size());
		
	}

}




3) Spring DI 개념 : 230228


a. DI 개념 :


  • IOC 컨테이너 : 보통, 흐름을 말한다. 객체를 담았다가 뺄 때, 다른 것을 담았다가 뺄수도 있고 아닐 수도 있다. 즉, 객체에 소스를 껴서 뺄 수도 있다.


  • 부품(Dependency = 의존성 = 데이터 소스)과 제품(템플릿?), 결합(Injection = 주입)


  • 작은 부품들이 조립되어 큰 부품으로 만들어져 간다. 이것을 “Inversion of Control”(제어의 역전이다.)라고 말한다. 코딩이 되어가는 순서이다.
    • 이것과 반대로 일체형 제품은 큰 부품에서 점차 작은 부품으로 만들어진다. 일반적인 우리가 만드는 프로그래밍이 실행되는 순서이다.


  • 우리는 조립을 해서 꺼내 쓸 수 있어야 한다.


  • 관계 : Composition 관계, Setter 결합 관계


  • Dependency + Injection : 부품(new B();) + 결합(setB();)


b. Bean 개념 :

  • 자바에서 Bean 객체를 자바 객체라고 불렀었다.


  • 어노테이션이 Bean 객체(콩자루)에 담겨지는 역할을 해준다! 추가로 라이브러리(mariaDB 등등)를 추가하자마자도 담긴다.
    • 스프링 부트에 새로운 데이터 베이스가 라이브러리에 담겨질 때, 기본 설정이 담겨져 있다. 하지만, 우리가 설정을 바꾸고 싶으면 application.properties에서 추가 설정을 해주어야 한다.(우리 DB로 바꿀 때!)


  • 특정 패키지가 있는데 그것이 package명이다.


  • DI의 지시서로 작성하는 방법 : XML 코드, 어노테이션 방법


c. Spring에 DI를 XML로 이용하는 방법 - Application Context :

  • 객체 지향에서 객체를 결합해주고 생성해주는 것이 필요하다.

  • 생성해주는 코드를 내 자바 코드에 넣는 것이 아니라 외부코드에서 생성해서 결합한다.


  • “IOC 컨테이너가 사용하는 Context”를 “Application Context”라고 한다. 서블릿 컨텍스트는 “어플리케이션 저장소”이며 “Application Context”와는 완전 다르다.(중요!!)**

  • Context : 작업을 하다가 다음에 계속 이어갈 수 있도록 담아두는 공간, “도구함”이라고 한다**


  • ClassPathXmlApplicationContext : xml 파일이 클래스 파일과 동일 경로에 있는 경우이며 ClassPathXmlApplicationContext 이외의 다른 ApplicationContext는 다른 경로에 xml 파일을 생성하자!
    • ClassPathXmlApplicationContext : root가 해당 개발환경이다.
    • FileSystemXmlApplicationContext : C드라이브나 등등에 위치
    • XmlWebApplicationContext : Web에 위치해있다. url 이용한다.
    • AnnotationConfigApplicationContext : Annotation으로 스캔하는 방법으로 위치해 있다.


  • IOC 컨테이너가 Application 컨텍스트를 읽어서 bean 태그로 빈(컨테이너)을 생성해서 getBean을 통해서 Bean을 꺼내서 읽을 수 있다.


  • XML를 이용한 Spring DI 실습 코드 1 :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean definitions here -->
	
	<bean class="kr.co.rland.web.repository.jdbc.JdbcMenuRepository">
	</bean>

</beans>



  • XML를 이용한 Spring DI 실습 코드 2 :
    • 이 부분이 DI에서 가장 중요하다!
	<!--  여기서 MbMenuRepository 빈 객체의 name 속성은 아래 DefaultMenuService 빈 객체의 name 속성과 연관 -->
 	<bean name="repository" class="kr.co.rland.web.repository.mybatis.MbMenuRepository" />
	
	<!-- DefaultMenuService 빈 객체의 name 속성은 기존 인터페이스에서 구현된 부분에서 setRepository()의 set과 ()을 날려버리고 첫 문자도 소문자로 바꿔서 넣어준다.
	// 또한, 우리가 자바 코드에서 실제로 로직에서 쓰는 것은 참조형이라서 ref에 repository를 넣어준다. -->
	<bean class="kr.co.rland.web.service.DefaultMenuService">
		<property name="repository" ref="repository" />
	</bean>



d. XML을 이용한 DI의 실습 코드(중요!) :

  • xml로 DI를 하기 위해서는 Application Context 종류에 따라서 알맞은 경로에 xml 파일을 만들어준다.
  • 각 계층(레이어)들을 결합해주기 위해서 빈 객체와 setter를 만들어서 결합해준다.
  • 또한, 연결하고 싶은 계층(레이어)에는 setter가 있어야 xml의 bean 태그에서 property 속성을 이용하여 다른 계층(레이어)들과 결합시킬 수 있다.
  • bean 태그의 property가 실제로 동작하기 위해서는 연결하고 싶은 계층의 객체에서 setter가 있어야 property 태그가 동작한다.


application.properties

spring.servlet.multipart.max-file-size=100MB	
#각각의 파일 용량 
spring.servlet.multipart.max-request-size=200MB 
#전체 용량

spring.datasource.url=jdbc:mariadb://db.newlecture.com:3306/rlanddb
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=rland
spring.datasource.password=20220823


Program.java
package kr.co.rland.web.di;

import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.service.MenuService;

public class Program {

	
	 public static void main(String args[]) {
		 ApplicationContext context = new ClassPathXmlApplicationContext("kr/co/rland/web/di/context.xml");
		 
//		 MenuRepository menuRepository = context.getBean(MenuRepository.class);
//		 List<Menu> list = menuRepository.findAll();

		 MenuService service = context.getBean(MenuService.class);
		 List<Menu> list = service.getList();
		 
		 
		 System.out.println(list);
	 }
}



MenuService.java
package kr.co.rland.web.service;

import java.util.List;

import kr.co.rland.web.entity.Menu;

public interface MenuService {
	
	// 서비스 계층에서는 사용자 요청을 이름 그대로 그대로 만들어라!!
	List<Menu> getList();	
}


DefaultMenuService.java
package kr.co.rland.web.service;

import java.util.List;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

public class DefaultMenuService implements MenuService {
	
	
	private MenuRepository repository;
	

	// 객체를 만들어서 결합해준다! 
	// setter가 있어야 xml에서 bean 태그에서 property 속성을 이용할 수 있다. 
	public void setRepository(MenuRepository repository) {
		this.repository = repository;
	}

	@Override
	public List<Menu> getList() {
		return repository.findAll();
	}

}


context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean definitions here -->
	
<!-- 	<bean class="kr.co.rland.web.repository.jdbc.JdbcMenuRepository">
	</bean>
 -->
 
 	<!--  여기서 MbMenuRepository 빈 객체의 name 속성은 아래 DefaultMenuService 빈 객체의 name 속성과 연관 -->
 	<bean name="repository" class="kr.co.rland.web.repository.mybatis.MbMenuRepository" />
	
	<!-- DefaultMenuService 빈 객체의 name 속성은 기존 인터페이스에서 구현된 부분에서 setRepository()의 set과 ()을 날려버리고 첫 문자도 소문자로 바꿔서 넣어준다.
	// 또한, 우리가 자바 코드에서 실제로 로직에서 쓰는 것은 참조형이라서 ref에 repository를 넣어준다. -->
	<bean class="kr.co.rland.web.service.DefaultMenuService">
		<property name="repository" ref="repository" />
	</bean>

</beans>


Menu.java
package kr.co.rland.web.entity;

import java.util.Date;

public class Menu {
	private long id;
	private String name;
	private int price;
	private Date regDate;
	private int categoryId;
	private long regMemberId;
	
	
	public Menu() {
	}
	
	
	public Menu(long id, String name, int price, Date regDate, int categoryId, long regMemberId) {
		this.id = id;
		this.name = name;
		this.price = price;
		this.regDate = regDate;
		this.categoryId = categoryId;
		this.regMemberId = regMemberId;
	}
	
	//insert용
	
	public Menu(String name, int price, int categoryId) {
		this.name = name;
		this.price = price;
		this.categoryId = categoryId;
	}


	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public int getPrice() {
		return price;
	}


	public void setPrice(int price) {
		this.price = price;
	}


	public Date getRegDate() {
		return regDate;
	}


	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}


	public int getCategoryId() {
		return categoryId;
	}


	public void setCategoryId(int categoryId) {
		this.categoryId = categoryId;
	}


	public long getRegMemberId() {
		return regMemberId;
	}


	public void setRegMemberId(long regMemberId) {
		this.regMemberId = regMemberId;
	}


	@Override
	public String toString() {
		return "Menu [id=" + id + ", name=" + name + ", price=" + price + ", regDate=" + regDate + ", categoryId="
				+ categoryId + ", regMemberId=" + regMemberId + "]";
	}
	
	
	
}


e. 어노테이션을 이용한 Spring DI 정리(간단히)

  • @Component는 IOC 컨테이너에서 새로운 빈 객체를 만들어 준다.
    • 보통 컨트롤러나 구현된 서비스 객체나 구현된 DAO에 붙는다.


  • @Autowired는 이렇게 생성된 빈 객체를 다른 빈 객체와 연결시켜준다.
    • 보통 붙이고 싶은 객체 선언부나 생성자에 붙고 기본 생성자가 있을때는 setter에 붙는다.



3. Spring DI - Annotation : 230302

1) Spring DI - XML 정리

  • 우리는 컨테이너에서 빈 객체를 만들어 낼 수 있다. xml 파서와 json 파서를 읽어 낼 수 있다.

  • 우리는 클래스명이 아니라 빈 객체의 name 속성의 형식으로도 가져와서 빈 객체를 읽을 수 있다.

  • 이렇게 하면, 원래는 인터페이스의 구현체인데 인터페이스명을 가져올수도 있다.


  • 실습 코드 :


Program.java
package kr.co.rland.web.di;

import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.service.MenuService;

public class Program {

	
	 public static void main(String args[]) {
		 ApplicationContext context = new ClassPathXmlApplicationContext("kr/co/rland/web/di/context.xml");
		 
//		 MenuRepository menuRepository = context.getBean(MenuRepository.class);
//		 List<Menu> list = menuRepository.findAll();

//		 MenuService service = context.getBean(MenuService.class);
		 
		 // 우리는 클래스명이 아니라 빈 객체의 name 속성의 형식으로도 가져와서 빈 객체를 읽을 수 있다.
		 // 원래는 인터페이스의 구현체인데 인터페이스명을 가져온다. 
		 MenuService service = (MenuService) context.getBean("service");
		 List<Menu> list = service.getList();
		 
		 
		 System.out.println(list);
	 }
}



context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean definitions here -->
	
<!-- 	<bean class="kr.co.rland.web.repository.jdbc.JdbcMenuRepository">
	</bean>
 -->
 
<!--  	여기서 name은 아래 DefaultMenuService의 name과 연관 -->
 	<bean name="repository" class="kr.co.rland.web.repository.mybatis.MbMenuRepository" />
	
	<!-- name에는 setRepository의 ()와 set을 날려버리고 소문자로 바꿔서 넣어주고 
	// 우리가 자바 코드에서 실제로 쓰는 것은 참조형이라서 ref에 repository를 넣어준다. -->
	<bean name="service" class="kr.co.rland.web.service.DefaultMenuService">
		<property name="repository" ref="repository" />
	</bean>

</beans>
  • xml로 만들면 단점이 외부파일에서 빈 객체를 등록해달라고 설정해야 한다. 그래서, 우리는 어노테이션으로 만든다. @Component 어노테이션을 이용한다.



2) 자바 어노테이션 개념


  • 어노테이션이란? : 코드에 심어놓는 설정이다. 즉, 처리기를 의미한다. 파서나 번역기를 만들 때, 코드에 심어준다.
  • 파싱을 xml을 통해서 직접 파싱하는 부분을 적어줘야 한다. 또한, xml은 미리 읽어야 한다.
  • 하지만, 어노테이션은 어노테이션이 작성되어 있는지만 먼저 확인해서 바로 파싱이 적용된다.


  • 실습 코드 1 :


Program.java
package kr.co.rland.web.di;

import java.util.Date;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import kr.co.rland.web.entity.JSONParser;
import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.service.MenuService;

public class Program {

	
	 public static void main(String args[]) {
//		 ApplicationContext context = new ClassPathXmlApplicationContext("kr/co/rland/web/di/context.xml");
		 
//		 MenuRepository menuRepository = context.getBean(MenuRepository.class);
//		 List<Menu> list = menuRepository.findAll();

//		 MenuService service = context.getBean(MenuService.class);
		 
		 // 이런 식으로 형식을 가져와서 빈 객체를 읽을 수 있다.  
//		 MenuService service = (MenuService) context.getBean("service");
//		 List<Menu> list = service.getList();
//		 
//		 
//		 System.out.println(list);
		 
		 
		 // 어노테이션 생성 과정 -> JSON parser, 리플렉션 개념 이용
		 Menu menu = new Menu();
		 menu.setId(1);
		 menu.setName("아메리카노") ;
		 menu.setPrice(3000);
//		 menu.setRegDate(new Date("2023-11-23"));	// Date 타입이 이상한듯?
		 
		 // 파서는 일반적으로는 Object를 사용한다! 
//		 JSONParser<Menu> json = new JSONParser<>(menu);
		 JSONParser parser = new JSONParser(menu);
		 String json = parser.toJSON();
		 
		 // 객체를 JSON식으로 변경(파싱)되어야 한다. {"id":1, "name":"아메리카노"}
		 System.out.println(json);
		 
		 
	 }
}


JSONParser.java
package kr.co.rland.web.entity;

import java.lang.reflect.Field;

public class JSONParser<T> {
	
//	public JSONParser(T entity) {
//		
//	}
	
	private	Object entity;
	
//	객체를 JSON으로 바꾸자! 보통은 제너릭말고 Object를 사용
	public JSONParser(Object entity) {
		this.entity = entity;
	}
	
	// 해당 객체를 생성자에서 넘겨 받거나 인자에서 타입으로 넘겨받는다.  
 	public String toJSON() {
 		
 		// Builder는 문자열을 더하는데 오버헤드를 줄여준다.
 		// 원래는 문자열을 더 하려면 문자열 끝을 찾기 위해서 완전 탐색한다. 이때, 오버헤드가 크게 발생! 
 		// 문자열 끝을 한 번에 찾아서 더해라!
 		StringBuilder builder = new StringBuilder();
 		
 		// JSON 형식이라서 첫 문자는 "{"이다.
 		builder.append("{");
 		
 		// 우리는 리플렉션을 통해 클래스 정보 중에서 필드를 돌면서 알맹이인 문자열을 찾자! 
 		Field[] fields = entity.getClass().getDeclaredFields();
 		
 		for(Field f: fields) {
 			String name = f.getName();
 			Object value = "v";
 			
 			String fieldValue = String.format("\"%s\":%s", name, value);
 			builder.append(fieldValue);
 			
 			// 마지막 문자열 처리 ? 
 			// if(?) 
 			
 			builder.append(",");
 		}

 		
 		// JSON 형식이라서 끝 문자는 "}"이다.
 		builder.append("}");
 		
 		
 		String json = builder.toString();
 		
 		// value와 어노테이션을 어떻게 처리할 것인지?
 		return json;
	}
}



  • 실습코드 2 :
    • Retention 생존 타임을 설정해줄 수도 있다. SOURCE(주석과 같은 역할이다.), CLASS(클래스가 살아있는 동안 라이프 사이클이며 RUNTIME과 라이프 사이클이 같다.), RUNTIME(JVM과 같은 라이프 사이클)


Menu.java
package kr.co.rland.web.entity;

import java.util.Date;

public class Menu {
	private long id;
	private String name;
	private int price;
	
	@Column("reg_date")	// 새로 만든 어노테이션 적용!
	private Date regDate;
	
	private int categoryId;
	private long regMemberId;
	
	
	public Menu() {
	}
	
	
	public Menu(long id, String name, int price, Date regDate, int categoryId, long regMemberId) {
		this.id = id;
		this.name = name;
		this.price = price;
		this.regDate = regDate;
		this.categoryId = categoryId;
		this.regMemberId = regMemberId;
	}
	
	//insert용
	
	public Menu(String name, int price, int categoryId) {
		this.name = name;
		this.price = price;
		this.categoryId = categoryId;
	}


	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public int getPrice() {
		return price;
	}


	public void setPrice(int price) {
		this.price = price;
	}


	public Date getRegDate() {
		return regDate;
	}


	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}


	public int getCategoryId() {
		return categoryId;
	}


	public void setCategoryId(int categoryId) {
		this.categoryId = categoryId;
	}


	public long getRegMemberId() {
		return regMemberId;
	}


	public void setRegMemberId(long regMemberId) {
		this.regMemberId = regMemberId;
	}


	@Override
	public String toString() {
		return "Menu [id=" + id + ", name=" + name + ", price=" + price + ", regDate=" + regDate + ", categoryId="
				+ categoryId + ", regMemberId=" + regMemberId + "]";
	}
	
	
	
}


Column.java
package kr.co.rland.web.entity;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// @interface는 어노테이션을 설정하는 방법 
// @Retention는 어노테이션의 생명주기를 설정해준다. 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
	
	String value();
}



JSONParser.java
package kr.co.rland.web.entity;

import java.lang.reflect.Field;

public class JSONParser<T> {
	
//	public JSONParser(T entity) {
//		
//	}
	
	private	Object entity;
	
//	객체를 JSON으로 바꾸자! 보통은 제너릭말고 Object를 사용
	public JSONParser(Object entity) {
		this.entity = entity;
	}
	
	// 해당 객체를 생성자에서 넘겨 받거나 인자에서 타입으로 넘겨받는다.  
 	public String toJSON() throws IllegalArgumentException, IllegalAccessException {
 		
 		// Builder는 문자열을 더하는데 오버헤드를 줄여준다.
 		// 원래는 문자열을 더 하려면 문자열 끝을 찾기 위해서 완전 탐색한다. 이때, 오버헤드가 크게 발생! 
 		// 문자열 끝을 한 번에 찾아서 더해라!
 		StringBuilder builder = new StringBuilder();
 		
 		// JSON 형식이라서 첫 문자는 "{"이다.
 		builder.append("{");
 		
 		// 우리는 리플렉션을 통해 클래스 정보 중에서 필드를 돌면서 알맹이인 문자열을 찾자! 
 		Field[] fields = entity.getClass().getDeclaredFields();
 		
 		for(Field f: fields) {
 			String name = f.getName();
 			
 			// 객체에 getAnotation이 있어야 어노태이션을 이용할 수 있다. 지금은, 어노테이션이 1개 뿐이라서 바로 가져 올 수 있다.
 			Column col = f.getAnnotation(Column.class);
 			
 			// col이 있을 때, 새로운 어노테이션을 적용해줄 수 있다.
 			if(col != null)
 				name = col.value();
 			
 			
 			// 리플렉션을 통해 private 클래스 정보도 접근 가능하게 해준다.
 			f.setAccessible(true);
 			Object value = 	f.get(entity);
 			
 			String fieldValue = String.format("\"%s\":%s", name, value);
 			builder.append(fieldValue);
 			
 			// 마지막 문자열 처리 ? 
 			// if(?) 
 			
 			builder.append(",");
 		}

 		
 		// JSON 형식이라서 끝 문자는 "}"이다.
 		builder.append("}");
 		
 		
 		String json = builder.toString();
 		
 		// value와 어노테이션을 어떻게 처리할 것인지?
 		return json;
	}
}




3) 스프링의 어노테이션을 이용한 Spring DI


a. 스프링이 제공하는 빈 객체 생성 어노테이션(in IOC Container)


  • @Component : 클래스에서 직접 빈 객체를 만들 때 사용한다. 지금은 소스코드 안에 들어갈 수 있다.
    • @Controller : 특화된 빈 객체 생성
    • @Service
    • @Repository
    • @Configuration :
      • @Bean : 이미 만들어 놓은 코드를 가져와서 새로운 코드에서 빈 객체를 만들어 줄 때, @Configuration를 통해 Bean 어노테이션을 사용한다.
      • 빈 객체에 담고 싶고 싶으면 return에 new로 그 객체의 이름을 반환하면 빈객체가 만들어 진다. 콩자루에 담긴다. IOC 컨테이너에 빈 객체가 생성된다.
      • 빈 객체를 생성하도록 해줄 수 있다.
      • 설정이 된 것을 담고 있어서 Configutation 어노테이션을 사용한다.
      • 위의 어노테이션은 모두 @Component와 대체된다.


  • 실습 코드 :


Program.java
package kr.co.rland.web.di;

import java.util.Date;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import kr.co.rland.web.entity.JSONParser;
import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.service.MenuService;

public class Program {
	
	 public static void main(String args[]) throws IllegalArgumentException, IllegalAccessException {
//		 ApplicationContext context = new ClassPathXmlApplicationContext("kr/co/rland/web/di/context.xml");

//		============  2. 어노테이션 기반의 Spring DI =========================		
		 
		 ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
		 
		 String hello = (String) context.getBean("hello");	// getBean 중요
		 
		 // String repository = (String) context.getBean("repository");	// getBean 중요, 
		 // @Bean이 붙은 메서드 명 그대로 쓰기!
		 
		 System.out.println(hello); 	// "hello 콩자루" 출력!
	}
}


Config.java
package kr.co.rland.web.di;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import kr.co.rland.web.repository.jdbc.JdbcMenuRepository;

// 스프링이 제공하는 객체 생성 어노테이션
// @Component :
	// @Controller
	// @Service
	// @Repository
	// @Configuration
		// @Bean

// @Configuration에 의해 먼저 메모리에 올라간다. 그리고 @Bean이라는 것도 있으면 콩자루에 담긴다.
// 마지막으로, @Configuration는 @Component와 모두 같기 때문에 @Controller와 @Service 등으로 바꾸어도 된다. 
// 여기서는 4개의 빈 객체가 IOC 컨테이너에 만들어진다. 
@Configuration
public class Config {		
	
	// @Bean만 간단히 쓰면 추가로 IOC 컨테이너에 빈 객체가 생성된다.
	// 반환이 new에 의해서 객체가 생성된다. 여기서 객체는 빈 객체이다. 
	// 이때, IOC 컨테이너가 이것을 읽을 수 있게 Annotation 어플리케이션 컨텍스트를 이용하자! 
	@Bean
	public JdbcMenuRepository repository() {
		return new JdbcMenuRepository();
	}
	
	@Bean
	public List list() {
		return new ArrayList();
	}
	
	@Bean
	public String hello() {
		return "hello 콩자루?";
	}
	
}




b. Spring 어노테이션을 Spring DI 연결


MenuController.java
package kr.co.rland.web.controller.admin;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import kr.co.rland.web.repository.MenuRepository;


@Controller("adminMenuController")
@RequestMapping("/admin/menu/")
public class MenuController {
	
	// 콩자루를 가져온다. 결합을 의미한다! 빈 객체 사용! 
	// 결합은 setInjection과 CompositionInjection이 있다.
	
	// 이것을 실행하려면 Application.java에서 실행해주자!
	// 객체가 같은 것이 2개가 있다면, DI하는데 문제가 있는 것이다. 그래서 @Qualifier로 구분해준다.
	// setter injection은 실행하거나 실행되는 영역이 생긴다. 바인딩 되기 전에 다른 작업을 처리할 수 있다.
	// 하지만, 코드 라인수가 가장 적은 '필드'에 @Autowired를 하여 DI를 한다. 
	@Autowired				// field injection도 가능하다. 그 이외에도 setter injection, contructor injection도 가능하다. 
	private MenuRepository menuRepository;
		
	
	public MenuController() {
		
	}
	
	
	public MenuController(MenuRepository menuRepository) {
		
		this.menuRepository = menuRepository;
	}
	

	public void setMenuRepository(MenuRepository menuRepository) {
		this.menuRepository = menuRepository;
	}
	
	// 400 에러는 데이터가 파라미터 인자가 없는 경우이다.
	// 403 에러는 권한 관련 에러이다. 
	// 405 에러는 처리메서드가 post요청인데 get요청만 있는 경우

	
	@RequestMapping("list")
	public String list(
			@RequestParam(name = "p", defaultValue = "1") int page, 
			
			@RequestParam(name = "q", required = false) String query,
			// @Requestparam같은 경우는 무조건 파라미터가 필요하다. 
			
			// 무조건을 없애주기 위해서 // @RequestParam(name = "q", required=false)		 
			
			// @RequestParam(name="q", defaultValue = "hello") String query,
			
			// 예전에는 스프링에서도 쿠키를 받아오기 위해서 request를 써야 한다.  
			//HttpServletRequest request
			
			// 지금은 스프링에서 이렇게 쿠키를 가져온다.  
			@CookieValue("my") String myCookie,
			
			// Request 헤더 요청! 
			@RequestHeader("Accept-Language") String language
			) throws UnsupportedEncodingException
	{
		System.out.printf("Accept-Language: %s\n", language);	
		// 출력 : Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7 
		
//		String myCookie = "";
//		
//		Cookie[] cookies = request.getCookies();
//		
//		for(Cookie cookie: cookies) 
//			if(cookie.getName().equals("my")) {
//				myCookie = cookie.getValue();
//				break;
//		}
		
		// 쿠키도 인코딩되어 있기 때문에 디코딩해서 우리는 사용한다. 
		// Ajax에서도 url도 인코딩해서 보낸다! post, get 요청!
		myCookie = URLDecoder.decode(myCookie, "utf-8");
		System.out.println(myCookie);
		
		System.out.printf("page: %d, query: %s\n",page, query);

		return "/WEB-INF/view/admin/menu/list.jsp";
	}
	
	@RequestMapping("detail")
	public String detail() {
		return "/admin/menu/detail";
	}
	
	// 등록폼을 주세요.
	// @RequestMapping("reg") 
	// -> service() 함수와 같다. : Get/Post를 내가 다 처리하는 매핑 
	
	@GetMapping("reg")
	public String reg() {
		return "/WEB-INF/view/admin/menu/reg.jsp";
	}
	
	// 폼입력해서 제출이요.
	@PostMapping("reg")
	public String reg(String title) {
		// 등록하고나서!
		System.out.println("메뉴 등록 완료");
		return "redirect:list";		
		// @Controller는 view단을 찾으므로 url을 입력해줘야한다. 
		// redirection은 reg라는 페이지에서 클라이언트가 list라는 url로 가라고 한다.
		// 경로가 더 이상 없다면 현재 경로에서 찾는다. 
		// 그 요청은 jsp파일의 form 태그에서 method post를 설정해줘야 한다. Post 요청이라서! 
	}
	
}


JdbcMenuRepository.java
package kr.co.rland.web.repository.jdbc;

import java.util.List;

import javax.sql.DataSource;

import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;


// @Component를 사용하면, 콩자루에 담긴다. 빈 객체 생성!

@Repository
public class JdbcMenuRepository implements MenuRepository {

	@Override
	public List<Menu> findAll() {
		
		
		String sql = "select id, name, price, regDate, categoryId from menu";

		DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
		dataSourceBuilder.driverClassName("org.mariadb.jdbc.Driver");
		dataSourceBuilder.url("jdbc:mariadb://db.newlecture.com:3306/rlanddb");
		dataSourceBuilder.username("rland");
		dataSourceBuilder.password("20220823");

		DataSource dataSource = dataSourceBuilder.build();

		JdbcTemplate template = new JdbcTemplate(dataSource);		
		List<Menu> list = template.query(sql, new BeanPropertyRowMapper(Menu.class)); // 메서드의 종류가 많다는 것은 좋은 신호이다.
		
		
		return list;
	}

	
	
//	@Override
//	public List<Menu> findAll() {
//		String sql = String.format("select id, name, price, regDate, categoryId from menu");
//		
//		List<Menu> list = new ArrayList<>();
//
//		try {
//			Class.forName("org.mariadb.jdbc.Driver");
//			String url = "jdbc:mariadb://db.newlecture.com:3306/rlanddb";
//			Connection con = DriverManager.getConnection(url, "rland", "20220823");
//
//			Statement st = con.createStatement();
//			ResultSet rs = st.executeQuery(sql);
//
//			// 필터링, 집계, 정렬
//			while (rs.next()) // 서버의 커서를 한칸 내리고 그 위치의 레코드를 옮겨 오는 것. -> 레코드 하나가 저장되는 공간은?
//			{
//				long id = rs.getInt("id");
//				String name = rs.getString("name");
//				int price = rs.getInt("price");
//				Date regDate = rs.getDate("regDate");
//				int categoryId = rs.getInt("categoryId");
//				
//				Menu menu = new Menu(id, name, price, regDate, categoryId, 1);
//				list.add(menu);
//			}
//			con.close();
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//		return list;
//	}

}




4) 최종 정리 :


  • 스프링 MVC : FrontController를 의미한다.
  • 스프링 DI : @Autowired, @Component -> Controller 계층을 Service 계층과 Repository 계층을 연결시켜준다.
  • 스프링 트랜잭션 : 어노테이션, AOP 이용
  • 스프링 시큐리티 : 필터 개념
  • ORM : Mybatis, JPA 이용



5) Mybatis

a. 기본 데이터 소스 정보

  • datasource는 스프링이 기본적으로 제공하는 DB 정보이다.

  • 실습 코드 :

    • Application.properties
spring.servlet.multipart.max-file-size=100MB	
#각각의 파일 용량 
spring.servlet.multipart.max-request-size=200MB 
#전체 용량

spring.datasource.url=jdbc:mariadb://db.newlecture.com:3306/rlanddb
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=rland
spring.datasource.password=20220823


  • JdbcMenuRepository.java
package kr.co.rland.web.repository.jdbc;

import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;


// @Component를 사용하면, 콩자루에 담긴다. 빈 객체 생성!

@Repository
public class JdbcMenuRepository implements MenuRepository {
	
	@Autowired
	private DataSource dataSource;
	
	@Override
	public List<Menu> findAll() {
		
		
		String sql = "select id, name, price, regDate, categoryId from menu";

//		DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
//		dataSourceBuilder.driverClassName("org.mariadb.jdbc.Driver");
//		dataSourceBuilder.url("jdbc:mariadb://db.newlecture.com:3306/rlanddb");
//		dataSourceBuilder.username("rland");
//		dataSourceBuilder.password("20220823");
//
//		DataSource dataSource = dataSourceBuilder.build();

		JdbcTemplate template = new JdbcTemplate(dataSource);		
		List<Menu> list = template.query(sql, new BeanPropertyRowMapper(Menu.class)); // 메서드의 종류가 많다는 것은 좋은 신호이다.
		
		
		return list;
	}

	
	
//	@Override
//	public List<Menu> findAll() {
//		String sql = String.format("select id, name, price, regDate, categoryId from menu");
//		
//		List<Menu> list = new ArrayList<>();
//
//		try {
//			Class.forName("org.mariadb.jdbc.Driver");
//			String url = "jdbc:mariadb://db.newlecture.com:3306/rlanddb";
//			Connection con = DriverManager.getConnection(url, "rland", "20220823");
//
//			Statement st = con.createStatement();
//			ResultSet rs = st.executeQuery(sql);
//
//			// 필터링, 집계, 정렬
//			while (rs.next()) // 서버의 커서를 한칸 내리고 그 위치의 레코드를 옮겨 오는 것. -> 레코드 하나가 저장되는 공간은?
//			{
//				long id = rs.getInt("id");
//				String name = rs.getString("name");
//				int price = rs.getInt("price");
//				Date regDate = rs.getDate("regDate");
//				int categoryId = rs.getInt("categoryId");
//				
//				Menu menu = new Menu(id, name, price, regDate, categoryId, 1);
//				list.add(menu);
//			}
//			con.close();
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//		return list;
//	}

}



b. Mybatis

  • Mybatis는 Repository 인터페이스의 구현부는 필요 없다. 지우자!


a) application.yml 설정!
  • 프로필 설정 : properties 파일말고 yml 파일 하나 추가하자! application.yml

  • yml(야믈) : 자바 스크립트의 JSON과 같은 표기를 가진다.
    • 장점 : 구조를 확인할 수 있다.
  • property로 만들어도 되고 yml로 만들어도 된다. 스펠링만 주의!

  • application.yml, application-dev.yml, application-prod.yml 라는 파일로 만들면 프로필을 또 만들 수 있다.


  • 동작 과정 : application-dev와 application-prod는 프로필이고 처음에는 root인 application.properties를 먼저 읽고 마지막에 application.yml이 읽히고 해당 yml 파일에 적힌 path를 통해서 특정 프로필인 application-dev.yml, application-prod.yml 등을 읽는다.


  • 보통, application.yml 파일은 설정 파일이 많아져서 복잡해질 때 쓰이고, JPA를 이용한 스프링 부트 프로젝트에서 쓰인다.**


  • 프로필 설정 방법 :
    • 실습 코드 :


  • application.properties
spring.servlet.multipart.max-file-size=100MB	
#각각의 파일 용량 
spring.servlet.multipart.max-request-size=200MB 
#전체 용량

spring.datasource.url=jdbc:mariadb://db.newlecture.com:3306/rlanddb
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=rland
spring.datasource.password=20220823


  • application.yml
  profiles:
    active:
    - dev
    - prod


  • application-dev.yml
spring:
  datasource:
    url: jdbc:mariadb://db.newlecture.com:3306/rlanddb
    driver-class-name: org.mariadb.jdbc.Driver
    username: rland
    password: 20220823
    



5. 복습 및 HTTP 강의 : 230303

1) HTTP :

  • 클라이언트로부터 다양한 요청이 들어오면 그에 맞게 HTTP 프로토콜에 따른 웹 서버만 바꿔준다.


2) Stream API :

  • 모든 데이터를 콜렉션으로 받기 때문에 이를 관리하려고 SQL문처럼 함수로 간단히 이용할 수 있다.


3) 구글 클래스룸 파일 확인하기