데이터융합 JAVA응용 SW개발자 기업 채용연계 연수과정 59일차 강의 정리

misung·2022년 6월 21일
0

스프링 MVC 웹서비스

1. 웹 어플리케이션 구조

2. Service, DAO 계층 구조

서비스 계층과 DAO 계층 어노테이션

  • @Component : 일반적인 컴포넌트로 등록되기 위한 클래스에 사용
  • @Controller : 컨트롤러 클래스에 사용
  • @Service : 서비스 클래스에 사용
  • @Repository : DAO 클래스 또는 리포지토리 클래스에 사용

위 어노테이션이 붙은 클래스를 빈으로 생성하기 위해서는 Service패키지 컴포넌트 스캔으로 등록해야 한다고 한다.

3. Service 구현

실습

DB 연동하지 않고 예제 진행해 봄.

ScoreVO 생성

com.spring.basic.model 패키지 밑에다 생성.

package com.spring.basic.model;

public class ScoreVO {

	private String stuName;
	private int kor;
	private int eng;
	private int math;
	private int total;
	private double average;
	
	// 총점, 평균 구하는 메서드
	public void calcData() {
		this.total = this.kor + this.eng + this.math;
		this.average = Math.round((this.total / 3.0) * 100) / 100.0;
	}

	public String getStuName() {
		return stuName;
	}

	public void setStuName(String stuName) {
		this.stuName = stuName;
	}

	public int getKor() {
		return kor;
	}

	public void setKor(int kor) {
		this.kor = kor;
	}

	public int getEng() {
		return eng;
	}

	public void setEng(int eng) {
		this.eng = eng;
	}

	public int getMath() {
		return math;
	}

	public void setMath(int math) {
		this.math = math;
	}

	public int getTotal() {
		return total;
	}

	public void setTotal(int total) {
		this.total = total;
	}

	public double getAverage() {
		return average;
	}

	public void setAverage(double average) {
		this.average = average;
	}
	
	
}

IScoreService 생성

com.spring.basic 패키지 밑에다 생성.

package com.spring.basic;

import java.util.List;

import com.spring.basic.model.ScoreVO;

public interface IScoreService {
	
	// 점수 등록 기능
	void insertScore(ScoreVO score);
	
	// 점수 전체 조회 기능
	List<ScoreVO> selectAllScores();
	
	// 점수 삭제 기능
	void deleteScore(int num);
	
	// 점수 개별 조회 기능
	ScoreVO selectOne(int num);
}

이제 기타 잡일 및 IScoreService 를 구현할 Service 클래스를 만들 차례다.

ScoreService 구현

com.spring.basic.service 패키지 밑에 생성

package com.spring.basic.service;

import java.util.List;

import com.spring.basic.IScoreService;
import com.spring.basic.model.ScoreVO;

public class ScoreService implements IScoreService {

	@Override
	public void insertScore(ScoreVO score) {
		

	}

	@Override
	public List<ScoreVO> selectAllScores() {
		
		return null;
	}

	@Override
	public void deleteScore(int num) {
		

	}

	@Override
	public ScoreVO selectOne(int num) {
		
		return null;
	}

}

아직 내용은 채우지 않았음.
컨트롤러에서 서비스에게 데이터를 전달해주면, 서비스는 데이터를 받아 DAO에 전달해야 한다.

4. DAO 구현

실습 이어서

IScoreDAO 구현

com.spring.basic.repository 패키지 하위에 생성.

서비스가 기본적으로 DB와 연동 작업을 진행하겠다 라고 목표를 가지고 작성한 메서드는, DAO도 똑같이 가지고 있어야 한다.
따라서 서비스 인터페이스를 받아와 구현하도록(implements) 하자.

package com.spring.basic.repository;

import java.util.List;

import com.spring.basic.model.ScoreVO;

public interface IScoreDAO {
		// 점수 등록 기능
		void insertScore(ScoreVO score);
		
		// 점수 전체 조회 기능
		List<ScoreVO> selectAllScores();
		
		// 점수 삭제 기능
		void deleteScore(int num);
		
		// 점수 개별 조회 기능
		ScoreVO selectOne(int num);
}

이런저런 메서드들에서 스코어서비스를 사용할 지 모르는데, 그때마다 선언할 수는 없는 데다가 하나만 선언해 두면 되므로 아노테이션을 추가한다.

ScoreService 수정

@Service
public class ScoreService implements IScoreService { ... }

@Service 아노테이션을 추가했다.

그러면 ScoreService 에서는 적절한 데이터의 가공 작업을 거친 후, DAO에게 데이터를 전송해줘야 하는데 마찬가지로 서비스의 모든 메서드에서 DAO를 새로이 선언할 수는 없다.

따라서, DAO를 빈으로 등록시켜놓고 서비스와 DAO간의 의존관계를 만들어 주어야 한다.

ScoreDAO 및 ScoreService 수정

package com.spring.basic.repository;

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

import org.springframework.stereotype.Repository;

import com.spring.basic.model.ScoreVO;

@Repository
public class ScoreDAO implements IScoreDAO {

	List<ScoreVO> scoreList = new ArrayList<>();
	
	@Override
	public void insertScore(ScoreVO score) {

	}

	@Override
	public List<ScoreVO> selectAllScores() {
		return null;
	}

	@Override
	public void deleteScore(int num) {

	}

	@Override
	public ScoreVO selectOne(int num) {
		return null;
	}

}

@Repository 아노테이션 사용해서 저장소와 연동 작업을 할 클래스임을 알려준다.

...
package com.spring.basic.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.basic.model.ScoreVO;
import com.spring.basic.repository.IScoreDAO;

@Service
public class ScoreService implements IScoreService {

	@Autowired
	private IScoreDAO dao;
	
	@Override
	public void insertScore(ScoreVO score) {
		score.calcData();
		System.out.println("service: " + score);
		dao.insertScore(score);
	}

	@Override
	public List<ScoreVO> selectAllScores() {
		
		return null;
	}

	@Override
	public void deleteScore(int num) {
		

	}

	@Override
	public ScoreVO selectOne(int num) {
		
		return null;
	} 

}
...

ScoreService 클래스의 멤버변수로 IScoreDAO를 선언하고 @Autowired 해 준다.

write-form.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>시험 점수 등록</h2>
	<form method="post">
		<p>
			# 이름: <input type="text" name="stuName"> <br>
			# 국어: <input type="text" name="kor"> <br>
			# 영어: <input type="text" name="eng"> <br>
			# 수학: <input type="text" name="math"> <br>
			<input type="submit" value="확인">
		</p>
	</form>
</body>
</html>

write-result.java

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>점수 등록 성공!</h2>
	<p>
		<%-- 
			<c:url value='uri 경로' />
			- value 속성 안에 컨텍스트 루트 경로를 제외한 절대 경로를 작성..
			- 컨텍스트 루트를 제외하고 연결이 가능. (컨텍스트 루트 변경 가능성 대비)
		--%>
		<a href="<c:url value='/score/register' />">다른 점수 등록하기</a>
		<a href="<c:url value='/score/list' />">점수 전체 조회하기</a>
		<a href="">점수 개별 조회하기</a>
	</p>
</body>
</html>

컨텍스트 루트 변경 시에 에러가 날 수 있어 jstl을 사용해서 현재 프로젝트의 컨텍스트 루트를 자동으로 읽어와주는 기능을 사용했다.

value 로 현재 컨텍스트 루트 경로를 제외한 절대 경로만을 넣어두면 된다.

점수 전체 조회 / 한개 삭제 메서드 구현

ScoreController.java

package com.spring.basic.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.spring.basic.model.ScoreVO;
import com.spring.basic.service.IScoreService;
import com.spring.basic.service.ScoreService;

@Controller
@RequestMapping("/score")
public class ScoreController {
	
	// 컨트롤러와 서비스 계층 사이의 의존성 자동 주입을 위한 변수 선언.
	@Autowired
	private IScoreService service;

	// 점수 등록 화면을 열어주는 처리를 하는 메서드
	@GetMapping("/register")
	public String register() {
		System.out.println("/score/register: GET");
		return "score/write-form";
	}
	
	// 점수 등록 요청을 처리할 메서드
	@PostMapping("/register")
	public String register(ScoreVO vo) {
		System.out.println("/score/register: POST");
		System.out.println("param: " + vo);
		service.insertScore(vo);
		return "score/write-result";
	}
	
	// 점수 전체 조회를 처리하는 요청 메서드
	@GetMapping("/list")
	public void list(Model model) {
		System.out.println("/score/list: GET");
		// List<ScoreVO> list = service.selectAllScores();
		model.addAttribute("sList", service.selectAllScores());
	}
	
	// 점수 삭제 요청 처리 메서드
	@GetMapping("/delete")
	public String delete(@RequestParam("stuNum") int stuNum,
						RedirectAttributes ra) {
		//삭제 처리를 완료하신 후 list 요청이 다시 컨트롤러로 들어갈 수 있도록 처리해 보세요.
	    //list요청이 다시 들어가서 list.jsp로 갔을 때, 삭제 이후에 간 것이 판단된다면
	    //브라우저에 '삭제가 완료되었습니다.' 문구를 빨간색으로 띄워보세요.
	    //(RedirectAttributes 사용, 경고창으로 띄워도 좋아요.)
		System.out.println("삭제할 학번 : " + stuNum);
		service.deleteScore(stuNum);
		
		ra.addFlashAttribute("msg", stuNum + "번 학생의 삭제가 완료되었습니다");
		return "redirect:/score/list";
	}
}

list() 메서드의 경우 Model 을 매개 변수로 받아 sList 라는 어트리뷰트를 만들고, 서비스의 selectAllScores() 메서드를 호출하게 한다.

그러면 서비스에서는 DAO 에 있는 같은 이름의 메서드를 부를 것이고, DAO 는 DB와의 작업을 진행하게 될 것이다.

delete 메서드 구현.
학교 기말떄문에 살짝 강의 따라가는데 어려움이 있었어서 어떻게 해야 하나 잠시 고민했음.

원래라면 DAO를 싱글턴으로 만들어 가져왔는데 이번엔 그렇게 안 했으니까 + 이미 어떻게 사용하는지 다 알려주셨는데 까먹어서 강의를 참고하니 Service 클래스를 호출하면 되는 거였음.

Service와 DAO는 구현할 메서드가 동일하기도 하고, Service에서는 단순히 DAO를 호출하는 역할을 하고 있고, Service 클래스는 DAO를 private로 들고 있기도 하므로 결국 Controller의 delete 메서드 안에서는 Service의 delete 메서드를 불러주면 됨.

ScoreService.java

package com.spring.basic.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.basic.model.ScoreVO;
import com.spring.basic.repository.IScoreDAO;

@Service
public class ScoreService implements IScoreService {

	@Autowired
	private IScoreDAO dao;
	
	@Override
	public void insertScore(ScoreVO score) {
		score.calcData();
		System.out.println("service: " + score);
		dao.insertScore(score);
	}

	@Override
	public List<ScoreVO> selectAllScores() {
		return dao.selectAllScores();
	}

	@Override
	public void deleteScore(int num) {
		dao.deleteScore(num - 1);
	}

	@Override
	public ScoreVO selectOne(int num) {
		
		return null;
	} 

}

ScoreService 클래스의 경우 다음과 같이 구현해 둔다.

insertScore() 메서드의 경우, ScoreVOcalcData() 를 불러 점수를 계산시켜 둔다.

// 총점, 평균 구하는 메서드
	public void calcData() {
		this.total = this.kor + this.eng + this.math;
		this.average = Math.round((this.total / 3.0) * 100) / 100.0;
	}

그리고 DAO 에 있는 같은 이름의 메서드를 부르고 score를 넘긴다.

selectAllScores() 메서드의 경우 따로 할 작업은 없으므로 호출만 하면 된다.

deleteScore() 메서드는 num - 1을 해 주고 있는데, 학생 번호는 표기 과정에서 +1을 해 주어 표기(1, 2, 3 ...)하고 있었으므로, 0부터 시작하는 Array의 index를 생각하면 -1을 해 주어 넘기면 되게 된다.

ScoreDAO.java

package com.spring.basic.repository;

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

import org.springframework.stereotype.Repository;

import com.spring.basic.model.ScoreVO;

@Repository
public class ScoreDAO implements IScoreDAO {

	List<ScoreVO> scoreList = new ArrayList<>();
	
	@Override
	public void insertScore(ScoreVO score) {
		scoreList.add(score);
	}

	@Override
	public List<ScoreVO> selectAllScores() {
		return scoreList;
	}

	@Override
	public void deleteScore(int num) {
		scoreList.remove(num);
	}

	@Override
	public ScoreVO selectOne(int num) {
		return null;
	}

}

ScoreDAO 의 각 메서드는 실제 DB와 통신하지 않고 현재 DB의 역할을 임시적으로 대신하는 List 에 의해 관리되므로 각 메서드의 동작이 복잡하지 않다.

검색 메서드 구현

스코어컨트롤러에 search 메서드 구현 후, search.jsp 생성

ScoreController.java

@GetMapping("/search")
	public void search() {
		System.out.println("/score/search: GET");
	}
	
	@GetMapping("/selectOne")
	public String selectOne(@RequestParam("stuNum") int stuNum,
							Model model,
							RedirectAttributes ra) {
		List<ScoreVO> list = service.selectAllScores();
		
		if (stuNum > list.size() || stuNum <= 0) {
			ra.addFlashAttribute("msg", "학번 정보가 없습니다.");
			return "redirect:/score/search";
		} else {
			model.addAttribute("stu", service.selectOne(stuNum));
			return "score/search-result";
		}
	}

일단 /search 페이지로 보내주는 처리가 필요하므로 @getMapping 을 해 두고, 실제로 점수를 찾아오는 일은 selectOne() 메서드에서 처리하도록 한다.

Model 의 개념을 벌써 까먹었는데, (일차상 하루 전 밖에 안 됐긴 하지만, 기말 때문에 약 일주일 정도를 학교 공부만 하다 왔다..) Model 객체는 화면에 데이터를 보낼 때 사용할 수 있고, forward() 를 하지 않아도 된다.

우리는 화면에 찾으려는 학번과 일치하는 학생의 정보를 보낼 것이므로, Model 객체의 도움이 필요하니 매개 변수에 Model 을 넣었다.

그리고 if ~ else 로 따로 처리를 좀 했는데,
학생 리스트를 받아와서 학생 리스트 크기를 넘는 학번을 입력받았으면, 당연히 학생이 존재하지 않을 테이므로 (물론 0번 이하의 학번 또한 처리) 학번 정보가 없다는 msg 를 날린다.

그게 아니라면 modeladdAttribute() 메서드를 이용하여 찾아낸 학생의 정보를 "stu" 로 이름지어 보낸다.

ScoreService.java

@Override
	public ScoreVO selectOne(int num) {
		return dao.selectOne(num - 1);
	} 

스코어서비스 클래스에 selectOne() 메서드를 구현한다.
아까 deleteScore() 와 마찬가지로, 학번은 1번부터 세고 있으므로 인덱스로 전달하려면 -1을 하여 전달한다.

ScoreDAO.java

@Override
	public ScoreVO selectOne(int num) {
		return scoreList.get(num);
	}

DAOService 측에서 이미 가공된 번호를 전달하고 있으므로 그대로 번호를 넣어서 찾은 뒤, 반환하기만 하면 된다.

이로써 학생의 점수를 입력받고, 찾고, 삭제하는 등의 과정을 구현해봄으로써 DB를 본격적으로 사용해보기 전의 흐름 파악을 위한 예제는 끝이 났다.

게시판 예제 실습

com.spring.basic.model 패키지에 BoardVO 클래스 생성

BoardVO.java

package com.spring.basic.model;

public class BoardVO {

	private String writer;
	private String title;
	private String content;
	
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
}

IBoardService.java

com.spring.basic.service 패키지에 IBoardService 생성

package com.spring.basic.service;

import java.util.List;

import com.spring.basic.model.BoardVO;

public interface IBoardService {

	// 게시글 등록
	void insertArticle(BoardVO vo);
	
	// 전체 게시글 목록
	List<BoardVO> getArticles();
	
	// 게시글 상세 보기
	BoardVO selectArticle(int bId);
	
	// 게시글 삭제
	void deleteArticle(int bId);
	
	// 게시글 수정
	void updateArticle(BoardVO vo, int bId);
}

BoardService.java

위와 같은 패키지에 생성.

package com.spring.basic.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.basic.model.BoardVO;
import com.spring.basic.repository.IBoardDAO;

@Service
public class BoardService implements IBoardService {

	@Autowired
	private IBoardDAO dao;
	
	@Override
	public void insertArticle(BoardVO vo) {
		
	}

	@Override
	public List<BoardVO> getArticles() {

		return null;
	}

	@Override
	public BoardVO selectArticle(int bId) {

		return null;
	}

	@Override
	public void deleteArticle(int bId) {


	}

	@Override
	public void updateArticle(BoardVO vo, int bId) {


	}

}

@Service 아노테이션 빼먹으면 안됨. 서비스 클래스로 등록해두고 의존성 주입하지 않으면 나중에 자바빈 못 찾아서 NullPointerException 뜰 수 있음!!

그리고 DAO 클래스를 들고 있어야 나중에 데이터 처리를 할 수 있으므로 private로 들고 있게 하고 마찬가지로 객체 자동 주입을 위해서 @Autowired 아노테이션을 추가해두자.

IBoardDAO.java

com.spring.basic.repository 패키지에 생성

package com.spring.basic.repository;

import java.util.List;

import com.spring.basic.model.BoardVO;

public interface IBoardDAO {
		// 게시글 등록
		void insertArticle(BoardVO vo);
		
		// 전체 게시글 목록
		List<BoardVO> getArticles();
		
		// 게시글 상세 보기
		BoardVO selectArticle(int bId);
		
		// 게시글 삭제
		void deleteArticle(int bId);
		
		// 게시글 수정
		void updateArticle(BoardVO vo, int bId);
}

내용은 IBoardService 인터페이스의 내용과 같다.

BoardDAO.java

위와 같은 repo 패키지에 생성.

package com.spring.basic.repository;

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

import org.springframework.stereotype.Repository;

import com.spring.basic.model.BoardVO;

@Repository
public class BoardDAO implements IBoardDAO {

	// DB 대용으로 게시글 저장할 리스트
	List<BoardVO> bList = new ArrayList<>();
	
	@Override
	public void insertArticle(BoardVO vo) {


	}

	@Override
	public List<BoardVO> getArticles() {

		return null;
	}

	@Override
	public BoardVO selectArticle(int bId) {

		return null;
	}

	@Override
	public void deleteArticle(int bId) {


	}

	@Override
	public void updateArticle(BoardVO vo, int bId) {


	}

}

IBoardDAO 인터페이스를 상속받아 생성한다.

얘도 @Repository 아노테이션을 빼 먹지 말자.

BoardController.java

com.spring.basic.controller 패키지에 생성

package com.spring.basic.controller;

import java.util.List;

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

import com.spring.basic.model.BoardVO;
import com.spring.basic.service.IBoardService;

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

	@Autowired
	private IBoardService service;
	
	// 글 작성 화면을 열어주는 메서드
	@GetMapping("/write")
	public void write() {
		System.out.println("/board/write: GET");
	}
	
	// 작성된 글 등록 처리 요청
	@PostMapping("/write")
	public String write(BoardVO board) {
		System.out.println("/board/write: POST");
		service.insertArticle(board);
		return "redirect:/board/list";
	}
	
	@GetMapping("/list")
	public void list(Model model) {
		System.out.println("/board/list: GET");
		model.addAttribute("articles", service.getArticles());
	}
}

글 작성 화면을 열고, 같은 url을 활용해서 POST 요청이 넘어온 경우 글 등록 처리를 한 다음, /list 쪽으로 redirect: 를 시켜 준다.

list() 메서드의 경우 model 을 통해서 화면에 데이터를 넘겨주도록 한다.

이제 ServiceDAO 를 건드려야 한다.

BoardService, BoardDAO 수정

BoardService.java

@Override
	public void insertArticle(BoardVO vo) {
		dao.insertArticle(vo);
	}

	@Override
	public List<BoardVO> getArticles() {
		return dao.getArticles();
	}

BoardDAO.java

@Override
	public void insertArticle(BoardVO vo) {
		articles.add(vo);
	}

	@Override
	public List<BoardVO> getArticles() {
		return articles;
	}

크게 어려운 부분은 없다.
이제 뷰 부분을 만들 차례다.

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>게시글 목록 조회</h2>
	
	<table border="1">
		<tr>
			<th>번호</th>
			<th>제목</th>
			<th>작성자</th>
			<th>비고</th>
		</tr>
		<c:forEach var="article" items="${articles}" varStatus="num">
		<tr>
			<td>${num.index + 1}</td>
			<td>
				<a href="<c:url value='/board/content?boardNo=${num.index+1}' />">${article.title}</a>
			</td>
			<td>${article.writer}</td>
			<td>
				<a href="">[삭제]</a>
			</td>
		</tr>
	</c:forEach>
	</table>
</body>
</html>

솔직히 코드보다 이쪽이 훨씬 쥐약이었다.
애초에 테이블 태그에 대해서는 알고 있긴 하지만 갑자기 구조를 생각하려니 (테이블 안에 <c:forEach> 등..) 답이 안 나와서 그냥 따라했다.

이제 글을 눌렀을 때, <a ...> 태그의 리다이렉트 될 주소에 따라 처리가 필요한데, /content 로 매핑되고 boardNo 를 넘겨주고 있으므로, 이것을 처리할 메서드를 BoardController 클래스에 만들러 가자.

BoardController.java 수정

...
// 글 내용 상세보기 요청 처리 메서드
	@GetMapping("/content")
	public void content(@RequestParam("boardNo") int bId,
						RedirectAttributes ra,
						Model model) {
		// 게시글 번호는 1번부터 세고 있고 (list.jsp 참조) List는 0번부터.
		ra.addFlashAttribute("boardNo", bId);
		model.addAttribute("article", service.selectArticle(bId - 1));
	}
...

글 내용 상세보기 메서드를 구현하였다.

물론 여기만 건든 건 아니고, 서비스와 DAO 또한 구현해놔야 한다.

BoardService, BoardDAO 수정

BoardService.java

...
@Override
	public BoardVO selectArticle(int bId) {
		return dao.selectArticle(bId);
	}
...

BoardDAO.java

...
@Override
	public BoardVO selectArticle(int bId) {
		return articles.get(bId);
	}
...

글 수정, 삭제 구현 (BoardController 수정)

비몽사몽해서 몰랐는데 위의 selectArticle은 getArticle이라는 이름의 메서드였음; 이름만 바뀌고 하는 동작은 똑가타을텐데 서비스 부분의 bId 를 곧이곧대로 DAO 한테 주지 않고, -1 한 상태로 넘겨줄것임. 대신 컨트롤러에서 bId를 줄때 이제 -1을 안 해도 됨.

그리고 Controller측 상세보기 메서드도 좀 바꿈..

// 글 내용 상세보기 요청 처리 메서드
	@GetMapping("/content")
	public void content(@ModelAttribute("boardNo") int boardNo,
						Model model) {
		System.out.println("/board/content?boardNo=" + boardNo);
		model.addAttribute("article", service.getArticle(boardNo));
	}

정신을 어따 두고 있는 건지 모르겠는데, ra 필요없고 ModelAttribute에 붙여서 보내면 됨..

BoardController.java

@GetMapping("/modify")
	public void modify(@ModelAttribute("boardNo") int boardNo, Model model) {
		System.out.println("수정페이지 이동 요청! 번호 : " + boardNo);
		model.addAttribute("article", service.getArticle(boardNo));
	}
	
	@PostMapping("/modify")
	public String modify(@RequestParam("boardNo") int boardNo, BoardVO vo) {
		System.out.println("글 수정 요청! 번호 : " + boardNo);
		service.updateArticle(vo, boardNo);
		return "redirect:/board/content?boardNo=" + boardNo;
	}
	
	@GetMapping("/delete")
	public String delete(@RequestParam("boardNo") int boardNo) {
		service.deleteArticle(boardNo);
		return "redirect:/board/list";
	}

글 수정 요청에 대한 GET, POST 매핑 처리를 해 두었고, delete 에 대한 처리도 해 두었다.

이에 대해서 Service 클래스와 DAO 클래스도 수정하였다.

BoardService.java

@Override
	public void deleteArticle(int bId) {
		dao.deleteArticle(bId - 1);
	}

	@Override
	public void updateArticle(BoardVO vo, int bId) {
		dao.updateArticle(vo, bId - 1);
	}

BoardDAO.java

@Override
	public void deleteArticle(int bId) {	
		articles.remove(bId);
	}

	@Override
	public void updateArticle(BoardVO vo, int bId) {
		// 배열 안에 든 녀석을 '바꾸고' 싶으면 set() 메서드 사용
		articles.set(bId, vo);
	}

이것으로 DB를 사용하지 않고 간단하게 게시물을 작성 - 조회 - 수정 - 삭제하는 실습을 끝마치도록 하겠다.

다음은 실제로 DB를 연동하는 실습이 진행된다.

스프링 MVC 웹서비스

1. JDBC

전통적인 JDBC 프로그래밍의 경우 다음과 같은 절차로 진행된다.

  1. Connection 객체 생성
  2. PrepareStatement 객체 생성
  3. SQL문 실행
  4. ResultSet 객체 생성 결과처리

커넥션 풀과 try-with-resource 문법을 사용함으로써 로직을 좀 줄여 보긴 했었지만, 이러한 절차는 너무 반복되는 작업이 계속되는 단점이 존재한다.

2. Spring-JDBC (템플릿)

Spring JDBC란? (JdbcTemplate)

  • JDBC의 장점을 유지하면서, 전통적 방식의 JDBC단점을 극복하여, 간결한 형태의 API 사용법을 제공하고 기존 방식에서 지원하지 않는 편리한 기능을 제공

  • Spring JDBC는 반복적으로 하는 작업을 대신한다
    (connection, prepareStatement, resultSet, resultSet의 반복 처리, Exception 처리)

  • Spring JDBC는 SQL에 바인딩할 값을 지정만 해 주면 된다

  • Spring JDBC 사용 전 DB커넥션을 가져오는 DataSource가 강제화 된다.

7:49:19 스프링 사이트 들어가는 부분까지 들어씅ㅁ

스프링 프로젝트 설정

www.spring.io 접속
상단 Projects 메뉴 - Spring Tools4 선택 - 최하단까지 스크롤 - Spring Tools 3 Suite 링크 선택 - 자바 8에서 실행할 수 있는 가장 높은 버전의 이클립스인 4.16 버전 밑의 4.15 설치 (Spring Tool Suite 3.9.13)

이쪽 링크를 눌러서 받아도 됨.
https://download.springsource.com/release/STS/3.9.13.RELEASE/dist/e4.15/spring-tool-suite-3.9.13.RELEASE-e4.15.0-win32-x86_64.zip

  1. 기존의 이클립스가 켜져있었다면 종료
  2. 받은 파일의 압축을 풀고, sts-3.9.13... 이라고 적인 폴더 선택
  3. STS.exe 파일 실행
  4. 기존의 이클립스에서 워크스페이스로 쓰던 ~~~/spring_work 를 워크스페이스로 지정.
  5. 경고문이 출력되지만 일단 무시하고 continue
  6. 기존 프로젝트가 실행이 안 되는 경우 Maven 업데이트를 하면 됨 (프로젝트 이름 우클릭 - Maven - Update Project)

그러면 왜 스프링을 설치했냐 하면,

이것 때문인데, (S가 표시된 파일과 폴더)


기존과 표시 방식이 크게 달라보이지 않지만,

이 탭이 추가되었다. 여기서 Beans Graph 탭을 눌러 보자.

등록되어있는 빈 객체가 기억이 나지 않거나 하는 경우에,
이렇게 한눈에 등록된 빈을 확인할 수 있는 이점이 있다.

그리고 또 있는데, servlet-context.xml의 source탭을 열어 보면,

<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

...  

문서 최상단에 이러한 코드를 볼 수가 있다.
이 부분은 우리가 필요한 기능이 있는 경우 spring 홈페이지에서 필요한 XML 네임스페이스 부분을 긁어와 붙였어야 했는데, 하단 namespaces 탭으로 들어가서

이렇게 필요한 네임스페이스에 체크 표시만 하면 된다.

이제 JDBC를 통한 실습을 진행해보도록 하자.

JDBC 실습

메이븐 디펜던시 업데이트

  1. File - New - Spring Legacy Project 선택
  2. 프로젝트 이름은 'SpringDBAccess'
  3. 하단 목록에서 Spring MVC Package 선택
  4. 최상위 패키지 명 : com.spring.db

이제 프로젝트를 생성 후 Maven Dependencies를 열어보면 알 수 있는 것이 스프링하고 자바 등의 것들이 버전이 좀 낮게 설정되어 있다. 이 부분을 고쳐주도록 하자.

  • <java-version> 1.8로 수정
  • <org.springframework-version> 5.3.18로 수정
  • <!-- Servlet --> 아래와 같이 부분 수정
<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
</dependency>
  • 아래의 <plugin> 부분에서 이 파트를 이렇게 수정
<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
</plugin>

이제 세이브하면 재로드 될 거고, 안 되면 Alt+F5나 프로젝트에 우클릭하여 Maven Update 할 것

db 관련 라이브러리 로드하기

우리가 사용하는 5.3.18 버전에 맞는 spring-jdbc 링크로 접속

https://mvnrepository.com/artifact/org.springframework/spring-jdbc/5.3.18

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.18</version>
</dependency>

이 코드를 가져와 pom.xml에 붙여넣기.

그리고 HikariCP를 가져올 건데 (커넥션 풀)

https://mvnrepository.com/artifact/com.zaxxer/HikariCP/3.3.1

많은 버전이 있긴 하지만 그냥 일단은 사용자가 많은 버전을 가져오도록 한다.

<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.3.1</version>
</dependency>

마찬가지로 pom.xml에 붙여넣기.

이제 OJDBC6도 가져올 것임. 오라클 11g 버전이라 ojdbc6을 가져오는 거긴 한데, 오라클 18c 이상이면 ojdbc8을 가져와야 한다.

어쨌든

https://mvnrepository.com/artifact/com.oracle/ojdbc6/11.2.0.2.0

이 링크로 접속하고

<!-- https://mvnrepository.com/artifact/com.oracle/ojdbc6 -->
<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>11.2.0.2.0</version>
</dependency>

이 코드를 복사해 pom.xml에 붙여넣는다.

그런데 이 코드에 무슨 문제가 있는지 로드가 안돼서

<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc6 -->
		<dependency>
		    <groupId>com.oracle.database.jdbc</groupId>
		    <artifactId>ojdbc6</artifactId>
		    <version>11.2.0.4</version>
		</dependency>

이쪽으로 로드하기로 했다.

그리고 아래로 스크롤해서, 테스트를 위한 JUnit 라이브러리 버전을 4.12로 갱신해준다.

<!-- Test -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>   

테스트

src/test/java 디렉토리의 com.spring.db 에 DBConnectionTest 클래스 생성.

아직 스프링의 커넥션 방법에 대해 배우지 않았으니 구식 방법으로 먼저 테스트를 진행해볼 것.

DBConnectionTest.java

package com.spring.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.junit.Test;

public class DBConnectionTest {
	private String driver = "oracle.jdbc.driver.OracleDriver";
	private String url = "jdbc:oracle:thin:@localhost:1521:xe";
	private String uid = "spring";
	private String upw = "spring";
	
	// DB 연결 테스트
	@Test
	public void connectTest() {
		Connection conn = null;
		
		try {
			Class.forName(driver);
			
			conn = DriverManager.getConnection(url, uid, upw);
			System.out.println("DB 커넥션 성공!");
			System.out.println("conn : " + conn);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

지금 이상태에서는 spring 이라는 계정명이 없으므로 계정을 만들어 주어야 한다.

Oracle SQLDeveloper를 쓰거나, cmd에 sqlplus를 입력해서 진행해도 되는데 cmd쪽으로 진행하겠다.

(sys 비밀번호를 까먹어서 초기화하느라 살짝 시간을 잡아먹었다. 초기화 방법은 인터넷 검색을 참고하자)

create user spring identified by spring;
grant create session to spring;
grant connect, resource to spring;
alter user spring default tablespace users quota unlimited on users;

방금 만든 클래스에서 우측 상단에 보면

이 outline이라는 perspective를 켜고,

Run As - JUnit Test를 눌러 실행할 것.
실행 대상은 connectTest() 메서드다.

테스트에 성공했다면 우측에 JUnit 창이 표시될 것이고, 성공했다면 바의 색깔이 초록, 실패했으면 빨간색이 된다.

그리고 실패 시, Failure Trace 창에 에러 로그가 표기된다.

테이블 생성

Oracle SQL Developer 프로그램을 켜서 위와 같은 새 커넥션을 만들어주자.

CREATE TABLE scores (
    stu_id NUMBER PRIMARY KEY,
    stu_name VARCHAR2(30) NOT NULL,
    kor NUMBER DEFAULT 0,
    eng NUMBER DEFAULT 0,
    math NUMBER DEFAULT 0,
    total NUMBER DEFAULT 0,
    average NUMBER(5, 2)
);

CREATE SEQUENCE id_seq
    START WITH 1
    INCREMENT BY 1
    MAXVALUE 1000
    NOCYCLE
    NOCACHE;

테이블과 시퀀스를 만들어두고 오늘의 강의는 종료하도록 한다.

0개의 댓글