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문처럼 함수로 간단히 이용할 수 있다.